22

I've noticed that at several places in our code base we use dynamically expanding arrays, i.e. a base array coupled with an element counter and a "max elements" value.

What I want to do is replace these with a common data structure and utility functions, for the usual object-oriented reasons. The array elements can be either basic data types or structs, I need fast random access to the elements, and preferably a type-safe implementation.

So, basically, what I would like to use is an STL vector, but the code base is restricted to C89 so I have to come up with something else :-)

I gave it some thought and whipped up this initial draft, just to show what I'm aiming at:

/* Type-safe dynamic list in C89 */

#define list_declare(type) typedef struct _##type##_list_t { type * base_array; size_t elements; size_t max_size; } type##_list_t
#define list(type) type##_list_t
#define list_new(type, initial_size) { calloc(initial_size, sizeof(type)), 0, initial_size }
#define list_free(list) free(list.base_array)
#define list_set(list, place, element) if ( list.elements < list.max_size ) { list.base_array[place] = element; } else { /* Array index out of bounds */ }
#define list_add(list, element) if ( list.elements < list.max_size ) { list.base_array[list.elements++] = element; } else { /* Expand array then add */ }
#define list_get(list, n) list.base_array[n]

/* Sample usage: */

list_declare(int);

int main(void)
{
    list(int) integers = list_new(int, 10);
    printf("list[0] = %d\n", list_get(integers, 0));
    list_add(integers, 4);
    printf("list[0] = %d\n", list_get(integers, 0));
    list_set(integers, 0, 3);
    printf("list[0] = %d\n", list_get(integers, 0));
    list_free(integers);

    return EXIT_SUCCESS;
}

...however, there must be someone else who has done this before. I'm aware of the FreeBSD sys/queue.h implementation of a similar concept for some different queues, but I can't find anything like that for arrays.

Is anyone here any wiser?

schot
  • 10,958
  • 2
  • 46
  • 71
Christoffer
  • 12,712
  • 7
  • 37
  • 53
  • 4
    At the very least, either get rid of the macros and replace them with functions, or fix them so that they work like functions. The latter involves wrapping any macro that's more than a single expression/statement with `do { ... } while (0)`. – R.. GitHub STOP HELPING ICE Aug 11 '10 at 09:03
  • 1
    Why would I want to get rid of the macros? Replacing them with functions would defeat the type independence, it would no longer be a generic solution. Also, why would I want to wrap with do ... while? That would make it impossible return values from the function-like macros. – Christoffer Aug 11 '10 at 10:12
  • 1
    @christoffer: Re-read R's comment. Note the use of "or" - those function macros are awful, you should improve them by "fixing" them, as R says. This makes using a function macro less surprising. I'd personally prefer if the function macros were capitalized, for good measure. – Arafangion Aug 11 '10 at 23:13

7 Answers7

12

glib provides an GArray type, which implements a dynamically growing array. If you can use external 3rd party libraries, glib is almost always a good choice as "standard" library for C. It provides types for all basic data structures, for unicode strings, for date and time values, and so on.

  • Good suggestion, but we can't use any third-party libraries (well, at least not one the size of glib at least). Also, the GArray does not seem to be type dependent, it looks possible to store objects of different types in one array as long as they have the same size :-) (such as 'int' and 'float') – Christoffer Aug 11 '10 at 10:16
  • 7
    @christoffer: Generic, but type safe containers can't be implemented in C. C's type system is too primitive and doesn't support any kind of generic types or templates. The only generic thing C has are void pointers, and these are not type safe. You have to get used to the fact, that C is simply a weakly typed language :) –  Aug 11 '10 at 14:13
  • @lunaryorn With a few tricks (e.g. type casts and `typeof`) it is possible to implement some pretty solid type safety. However, I do agree that it's going to be nearly impossible to implement type safe datatypes in C. – yyny Aug 23 '16 at 20:52
3

There is sglib, which implements various lists,hashmaps and rbtrees in a generic fashion (i.e. by specializing over a type). There is also a fast sorting function for arrays:

Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
3

here a simple vector-replacement, its ONE function for all, its strictly C89 and threadsafe; libs are too difficult for me, i use my own; no performance, but easy to use

/* owner-structs too */
typedef struct {
  char name[20],city[20];
  int salary;
} My,*Myp;

typedef char Str80[80];

/* add here your type with its size */
typedef enum {SPTR,INT=sizeof(int),DOUBLE=sizeof(double),S80=sizeof(Str80),MY=sizeof(My)} TSizes;

typedef enum {ADD,LOOP,COUNT,FREE,GETAT,GET,REMOVEAT,REMOVE} Ops;

void *dynarray(char ***root,TSizes ts,Ops op,void *in,void *out)
{
  size_t d=0,s=in?ts?ts:strlen((char*)in)+1:0;
  char **r=*root;
  while( r && *r++ ) ++d;
  switch(op) {
  case ADD:   if( !*root ) *root=calloc(1,sizeof r);
              *root=realloc(*root,(d+2)*sizeof r);
              memmove((*root)+1,*root,(d+1)*sizeof r);
              memcpy(**root=malloc(s),in,s);
              break;
  case LOOP:  while( d-- ) ((void (*)(char*))in)((*root)[d]); break;
  case COUNT: return *(int*)out=d,out;
  case FREE:  if(r) {
                ++d; while( d-- ) realloc((*root)[d],0);
                free(*root);*root=0;
              } break;
  case GETAT: { size_t i=*(size_t*)in;
                if(r && i<=--d)
                  return (*root)[d-i];
              } break;
  case GET:   { int i=-1;
                while( ++i,d-- )
                if( !(ts?memcmp:strncmp)(in,(*root)[d],s) )
                  return *(int*)out=i,out;
                return *(int*)out=-1,out;
              }
  case REMOVEAT: { size_t i=*(size_t*)in;
                   if(r && i<=--d) {
                     free((*root)[d-i]);
                     memmove(&(*root)[d-i],&(*root)[d-i+1],(d-i+1)*sizeof r);
                     return in;
                   }
                 } break;
  case REMOVE: while( *(int*)dynarray(root,ts,GET,in,&d)>=0 )
                 dynarray(root,ts,REMOVEAT,&d,0);
  }
  return 0;
}

void outmy(Myp s)
{
  printf("\n%s,%s,%d",s->name,s->city,s->salary);
}

main()
{
  My    z[]={{"Buffet","Omaha",INT_MAX},{"Jobs","Palo Alto",1},{"Madoff","NYC",INT_MIN}};
  Str80 y[]={ "123","456","7890" };
  char **ptr=0;
  int x=1;

  /* precondition for first use: ptr==NULL */
  dynarray(&ptr,SPTR,ADD,"test1.txt",0);
  dynarray(&ptr,SPTR,ADD,"test2.txt",0);
  dynarray(&ptr,SPTR,ADD,"t3.txt",0);

  dynarray(&ptr,SPTR,REMOVEAT,&x,0); /* remove at index/key ==1 */
  dynarray(&ptr,SPTR,REMOVE,"test1.txt",0);

  dynarray(&ptr,SPTR,GET,"t3.txt",&x);
  dynarray(&ptr,SPTR,LOOP,puts,0);

  /* another option for enumerating */
  dynarray(&ptr,SPTR,COUNT,0,&x);
    while( x-- )
      puts(ptr[x]);
  dynarray(&ptr,SPTR,FREE,0,0); /* frees all mallocs and set ptr to NULL */

  /* start for another (user)type */
  dynarray(&ptr,S80,ADD,y[0],0);
  dynarray(&ptr,S80,ADD,y[1],0);
  dynarray(&ptr,S80,ADD,y[2],0);
  dynarray(&ptr,S80,ADD,y[0],0);
  dynarray(&ptr,S80,LOOP,puts,0);
  dynarray(&ptr,S80,FREE,0,0); /* frees all mallocs and set ptr to NULL */

  /* start for another (user)struct-type */
  dynarray(&ptr,MY,ADD,&z[0],0);
  dynarray(&ptr,MY,ADD,&z[1],0);
  dynarray(&ptr,MY,ADD,&z[2],0);
  dynarray(&ptr,MY,ADD,&z[0],0);
  dynarray(&ptr,MY,LOOP,outmy,0);
  dynarray(&ptr,MY,FREE,0,0);

  return 0;
}
user411313
  • 3,930
  • 19
  • 16
2

qLibc implements a vector in pure C. The data structure allows it to store any type of object like (void *object) and it provides convenient wrappers for string, formatted string and integer types.

Here's a sample code for your idea.

qvector_t *vector = qvector(QVECTOR_OPT_THREADSAFE);
vector->addstr(vector, "Hello");
vector->addstrf(vector, "World %d", 123);
char *finalstring = vector->tostring(vector);

printf("%s", finalstring);
free(finalstring)
vector->free(vector);

for object type:

int a = 1, b = 2;
qvector_t *vector = qvector(QVECTOR_OPT_THREADSAFE);
vector->add(vector, (void *)&a, sizeof(int));
vector->add(vector, (void *)&b, sizeof(int));
int *finalarray = vector->toarray(vector);

printf("a = %d, b = %d", finalarray[0], finalarray[1]);
free(finalarray)
vector->free(vector);

Note) I made this sample code just for your reference, copying from its example code. it might have typo errors.

You can check out the Full API reference at http://wolkykim.github.io/qlibc/

user3474832
  • 251
  • 2
  • 5
2

I'm using the following macro implementation without problems so far. It isn't a complete implementation but grows the array automatically :

#define DECLARE_DYN_ARRAY(T) \
    typedef struct \
    { \
        T *buf; \
        size_t n; \
        size_t reserved; \
    } T ## Array;

#define DYN_ARRAY(T) T ## Array

#define DYN_ADD(array, value, errorLabel) DYN_ADD_REALLOC(array, value, errorLabel, realloc)

#define DYN_ADD_REALLOC(array, value, errorLabel, realloc) \
    { \
        if ((array).n >= (array).reserved) \
        { \
            if (!(array).reserved) (array).reserved = 10; \
            (array).reserved *= 2; \
            void *ptr = realloc((array).buf, sizeof(*(array).buf)*(array).reserved); \
            if (!ptr) goto errorLabel; \
            (array).buf = ptr; \
        } \
        (array).buf[(array).n++] = value; \
    }

To use you first write: DECLARE_DYN_ARRAY(YourType)

To declare variables you write DYN_ARRAY(YourType) array = {0}.

You add elements with DYN_ADD(array, element, errorLabel).

You access elements with array.buf[i].

You get the number of elements with array.n.

When done you free it with free(array.buf) (or whatever function you used to allocate it.)

Calmarius
  • 18,570
  • 18
  • 110
  • 157
1

I usually roll my own code for purposes such as this, like you did. It's not particularly difficult, but having type safety etc. is not easily achievable without a whole OO framework.

As mentioned before, glib offers what you need - if glib2 is too big for you, you could still go with glib1.2. It's quite old, but doesn't have external dependencies (except for pthread if you need thread support). The code can also be integrated into larger projects, if necessary. It's LGPL licensed.

onitake
  • 1,369
  • 7
  • 14
1

Personally, I prefer "Gena" library. It closely resembles stl::vector in pure C89.

It is comfortable to use because you can:

  • Access vector elements just like plain C arrays: vec[k][j];
  • Have multi-dimentional arrays;
  • Copy vectors;
  • Instantiate necessary vector types once in a separate module, instead of doing this every time you needed a vector;
  • You can choose how to pass values into a vector and how to return them from it: by value or by pointer.

You can check it out here:

https://github.com/cher-nov/Gena

user7369463
  • 165
  • 1
  • 8