6
\$\begingroup\$

I recently have been working on moving from projects written in Java and C++ to C and realized that the lack of std::vector in C makes it a bit more difficult to create multi-dimensional arrays. Also, without the std::string in C, it's harder to store arrays of strings and keep track of their sizes. To automate the process, I created a typedef'd struct: varray. varray attempts to emulate some basic functions of std::vector, e.g. storing sizes of the elements in an easily accessible location and allowing for allocation of multi-dimensional objects with "relative ease".

I've attached the header "varray.h" and a bit of sample code with comments to demonstrate its use. Please note that at this point, I haven't tried to dynamically expand the arrays, though I'll look into it as I practice more with the language.

If there is any feedback (syntax, memory allocation, anything), I'd greatly appreciate it - I'm still just getting started with C, C++, and Java.

varray.h

#ifndef VARRAY_H
#define VARRAY_H
#define VARRAY_DISPLAYDEBUG 0
#define VARRAY_STRSIZE(x) ((sizeof(char) * strlen(x)) + 1)
#include <stdlib.h>
#include <string.h>
#include <stdio.h> 

typedef struct {
    int* getInt;
    char* getChar;
    double* getDbl;
    char* str;
    int size;
} varray;

typedef enum {
    v_char = 0,
    v_int = 1,
    v_double = 2,
    v_varray = 3,
    v_tdvarray = 4
} varrayType;

void* createArray(varrayType arrayType, int arraySize);
varray varrayPush(const char* data);
varray allocateNumArray(varrayType type, const int size);
varray allocateCharArray(const int size);
varray* allocateVarray(const int size);
varray** allocateTDVarray(const int size);

inline void* createArray(varrayType arrayType, int arraySize) {
    varray* target;
    varray** vtarget;
    varray*** tdvtarget;
    if (arrayType == v_char) {
        target = malloc(sizeof(varray) * arraySize);
        *target = allocateCharArray(arraySize);
        return target;
    }
    else if (arrayType == v_int || arrayType == v_double) {
        target = malloc(sizeof(varray) * arraySize);
        *target = allocateNumArray(arrayType, arraySize);
        return target;
    }
    else if (arrayType == v_varray) {
        vtarget = malloc(sizeof(varray*) * arraySize);
        *vtarget = allocateVarray(arraySize);
        return *vtarget;
    }
    else if (arrayType == v_tdvarray) {
        tdvtarget = malloc(sizeof(varray**) * arraySize);
        *tdvtarget = allocateTDVarray(arraySize);
        return *tdvtarget;
    }
    else {
        return NULL;
    }
}   

inline varray varrayPush(const char* data) {
    varray target;
    if (VARRAY_DISPLAYDEBUG) {
        printf("%s%d%s%s\n", "Allocating array with size: ", (int)VARRAY_STRSIZE(data) - 1, " with contents: ", data);
    }
    target.str = malloc(VARRAY_STRSIZE(data));
    strcpy(target.str, data);
    if (VARRAY_DISPLAYDEBUG) {
        printf("%s%s\n", "String created successfully. Contents reads: ", target.str);
        printf("%s%d\n", "Memory address: ", (int)target.str);
    }
    target.size = VARRAY_STRSIZE(data);
    return target;
}

inline varray allocateNumArray(varrayType type, const int size) {
    int i;
    varray target;
    if (type == v_int) {
        if (VARRAY_DISPLAYDEBUG) {
            printf("%s%d\n", "Allocating array of type v_int with size ", size);
        }
        target.getInt = malloc(sizeof(int) * size);
        for (i = 0; i < size; i++) {
            target.getInt[i] = 0;
        }
    }
    else if (type == v_double) {
        if (VARRAY_DISPLAYDEBUG) {
            printf("%s%d\n", "Allocating array of type v_double with size ", size);
        }
        target.getDbl = malloc(sizeof(double) * size);
        for (i = 0; i < size; i++) {
            target.getDbl[i] = 0.0;
        }
    }
    target.size = size;
    return target;
}

inline varray allocateCharArray(const int size) {
    varray target;
    int i;
    if (VARRAY_DISPLAYDEBUG) {
         printf("%s%d\n", "Allocating array of type v_char with size ", size);
    }
    target.getChar = malloc(sizeof(char) * size);
    for (i = 0; i < size; i++) {
        target.getChar[i] = 0;
    }
    target.size = size;
    return target;
}

inline varray* allocateVarray(const int size) {
    varray* target = malloc(sizeof(varray) * (size + 1));
    if (VARRAY_DISPLAYDEBUG) {
        printf("%s%d\n", "Allocated array of type v_varray with size ", size);
    }
    target[0].size = size;
    return target;
}

inline varray** allocateTDVarray(const int size) {
    varray** target = malloc(sizeof(varray*) * (size + 1));
    if (VARRAY_DISPLAYDEBUG) {
        printf("%s%d\n", "Allocated array of type v_tdvarray with size ", size);
    }
    target[0] = malloc(sizeof(varray));
    target[0][0].size = size;
    return target;
}

#endif

Example implementation

#include "varray.h"

void varrayDemo() {
    varray sampleInt;
    /*
     * Functions as a normal array but also stores
     * the size of the array at sampleInt.size.
     * Accessing data elements used the syntax
     * VARRAY.getInt[POSITION]
     */
    varray* sampleString;
    /*
     * Since a C string is an array of char, creating
     * an array of varray objects can easily store multiple
     * strings. The size of the varray array can be accessed
     * via VARRAY[0].size Individual elements can be 
     * accessed at VARRAY[POSITION].str with position >= 1
     */
    varray** sampleContainer;
    /*
     * The container of varrays can store multiple data types
     * at each element's position. For example, the syntax
     * VARRAY[1][1].str will return the string, if stored.
     * while VARRAY[2][2].getDbl[POSITION] returns the double
     */
    sampleInt = *(varray*)createArray(v_int, 5);
    /*
     * The function createArray(TYPE, SIZE) initializes the array
     * NOTE: I am not sure if there is a more "aesthetically pleasing"
     * way to initialize the array, since createArray returns a void
     * pointer. When initialized, all values are set to 0
     */
    printf("The size of sampleInt is: %d\n", sampleInt.size);
    printf("The data at position 0 is: %d\n", sampleInt.getInt[0]);

    sampleString = createArray(v_varray, 2);
    /*
     * Each varray can contain one string. Initialize each element
     * with the createArray command.
     */
    printf("The size of sampleString is: %d\n", sampleString[0].size);
    /*
     * As noted above, the size is stored in a varray at position 0
     * in the container
     */
    sampleString[1] = varrayPush("This is a sample string");
    sampleString[2] = varrayPush("This is also a sample string!"); 
    /*
     * To store a string within a varray, the function varrayPush is used
     * with the desired string as the argument. The function initializes
     * another varray object within the container.
     */
    printf("The string at position 1 is: %s\n", sampleString[1].str);
    printf("The size of the string stored at position 1 is: %d\n", sampleString[1].size); 
    printf("The char at position 5 in sampleString[1] is: %c\n", sampleString[1].str[5]);

    sampleContainer = createArray(v_tdvarray, 2);
    sampleContainer[1] = createArray(v_varray, 1);
    sampleContainer[1][1] = *(varray*)createArray(v_double, 5);
    sampleContainer[1][1].getDbl[4] = 3.14;
    sampleContainer[2] = createArray(v_varray, 1);
    sampleContainer[2][1] = varrayPush("yet another sample string");
    /*
     * As noted with the original sampleInt example, the *(varray*)
     * syntax is used again.
     */
    printf("sampleContainer[1][1] has size: %d with data %lf at position 4\n", sampleContainer[1][1].size, sampleContainer[1][1].getDbl[4]);
    printf("sampleContainer[2][1] has size %d with string \"%s\"\n", sampleContainer[2][1].size, sampleContainer[2][1].str); 
}
\$\endgroup\$
  • \$\begingroup\$ Committed some changes suggested by @vnp Fixed initial allocation of memory in createArray, changed size from type int to size_t, implemented calloc to zero out memory in arrays, and used strdup in the varrayPush function. \$\endgroup\$ – Sam Andersen Feb 10 '16 at 3:18
  • \$\begingroup\$ Created the following with the updated code (suggestions from vnp) GitHub/vector.h \$\endgroup\$ – Sam Andersen Feb 10 '16 at 3:36
5
\$\begingroup\$
  • To much memory

        target = malloc(sizeof(varray) * arraySize);
        *target = allocateCharArray(arraySize);
    

    allocates that many varray entries, and uses just one of them. If you insist on double indirection,

        target = malloc(sizeof(varray));
    

    is just enough. OTOH I don't see a need for a double indirection at all.

  • All pointers smell the same

    There is no need to have pointers to all possible types. A single void * data would serve the same purpose. You should have getWhatever as accessor functions. You may want to associate the accessor function with the varray instance, eg:

        typedef struct varray {
            void * (*get)(struct varray * v, size_t index);
            void * data;
            size_t size;
        }
    
        struct varray * allocateCharArray(size_t size)
        {
            ....
            v->get = vGetChar;
        }
    

    where

        void * vGetChar(varray * v, size_t index) {
            return (char *) v->data + index;
        }
    
  • Missing deallocations

    Client has no resources to deallocate varrays.

  • Use standard library

    To zero out allocated memory, use calloc. To copy a string, use strdup.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I committed some of the suggestions provided; however, I'm not entirely sure that I understand the meaning of void * (*get)(struct varray * v, size_t index). If using getWhatever, what would the syntax be for setting and accessing the elements? - Thanks \$\endgroup\$ – Sam Andersen Feb 10 '16 at 3:21
3
\$\begingroup\$

C99 enables you to use flexible array members in struct-ures. I believe they are more suitable for your needs.

Perhaps you might consider

struct chararray {
  varrayType vtype; // when vtype is vchar 
  unsigned len;
  char chararr[];
};
struct intarray {
  varrayType vtype; // when vtype is vint
  unsigned len;
  int intarr[];
};
/// etc...
union varrayptr {
  varrayType* vtypep;
  chararrar* charrp;
  intarray* intarrp;
};

As an example, here is a safe accessor to the n-th char

int safe_nth_char (union varrayptr vp, int rk) {
  if (!vp.vtypep || *vp.vtypep != vchar) return EOF;
  unsigned l = vp->charrp.len;
  if (rk < 0  || rk >= (int)l) return EOF;
  return vp->charrp.chararr[rk];
}

Read more about tagged unions.

But I don't understand what exactly do you want. Some "universal" any type?

Of course, with flexible array members, you would reallocate a new pointer, not reuse the same one, when you need to grow the array.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ flexible array members would make resizing the array difficult. It's memory address would change \$\endgroup\$ – nishantjr Feb 10 '16 at 8:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.