top | item 10644637

Implementing a sort of generic, sort of type-safe array in C

20 points| ingve | 10 years ago |kirbyfan64.github.io | reply

10 comments

order
[+] 0x09|10 years ago|reply

   printf("%zu %d\n", list_len(l), *l[0]); // Prints 1 1.
This prints raptors unless your list is of size_t.

   #define list_lenref(l) ((size_t*)l)[-1]
The only way to write this kind of size-prefixed plain array in legal C will involve separate getter/setters to explicitly copy the size in and out. It's not as simple as getting a reference because you cannot recast an arbitrary array as a pointer to size_t and read/write to it. A compiler might be happy to optimize these accesses out altogether.

   size_t list_len(void* l) {
      size_t tmp;
      memcpy(&tmp, ((char*)l) - sizeof(size_t), sizeof(size_t));
      return tmp;
   }
   void list_set_len(size_t length) {
      memcpy(((char*)l) - sizeof(size_t), &s, sizeof(size_t));
   }
Or if you prefer crazy macros

   #define list_len(l) (*(size_t*)memcpy(&(size_t){0},((char*)l)-sizeof(size_t),sizeof(size_t)))
   #define list_set_len(l,s) memcpy(((char*)l)-sizeof(size_t),&(size_t){s},sizeof(size_t))
Note the ((char* )l). Pointer arithmetic on void* as used elsewhere in the code is a GNU extension. This is easily fixed by instead casting to char* or u/intptr_t.

Otherwise this style of list is just fine, in fact it's not necessarily limited to pointer types.

[+] opk|10 years ago|reply
What are "raptors"?
[+] abcd_f|10 years ago|reply
1. Not using container_of/struct_of, but rather peppering code with "(void* )l-sizeof(size_t)"

2. my_list_type.my_array is not properly aligned

3. list_append() doesn't handle realloc() failures

4. Doesn't properly () macro argument and lots of macros also reuse their arguments. That sure is going to be fun to debug, e.g. list_len(l++);

In short, if this works for the author - great, but the re-usability of this code is next to zero. The beauty of C is that it's very explicit and verbose. Once you start concealing things behind abstract APIs, you start trading verbosity for implicit invariants leading to obscure bugs and intense head scratching. Not a worthy trade off in hell of a lot of cases.

[+] opk|10 years ago|reply
As the article states, this only supports lists of pointer types. The result would be the same bad performance that Bjarne Stroustrup warns of in the cited talk on linked lists. Each of these pointers will be random memory positions giving you bad cache hits.

list_append needn't take a second argument that is assigned across. If you've got an array of structs, you can then initialise the struct members in place.

[+] to3m|10 years ago|reply
My growable array "system" is more along these lines:

    #define CONCAT(A,B) A##B
    #define ALEN_NAME(N) CONCAT(N,_length)
    #define ACAP_NAME(N) CONCAT(N,_capacity)
    #define ARRAY(T,N)       \
        T *N;                \
        size_t ALEN_NAME(N); \
        size_t ACAP_NAME(N)
    #define AINIT(N) do {N=NULL;ALEN(N)=0;ACAP(N)=0;}while(0)
    #define ADESTROY(N) do{free(N);AINIT(N);}while(0)
    #define ALEN(N) (ALEN_NAME(N))
    #define ACAP(N) (ACAP_NAME(N))
    #define AADD(N,V)                                                        \
        do {                                                                 \
            if(ALEN(N)>=ACAP(N)) {N=Grow(&ACAP(N),(N),ALEN(N),sizeof *N,1);} \
            N[ALEN(N)++]=V;                                                  \
        } while(0)
(etc., etc.)

I was a bit unsure about it at first, but having used it for a few months I've ended up quite happy with it:

- Easy to examine in a debugger;

- Code is fairly straightforward (taking into account the usual macro token pasting nonsense);

- Less risk of falling foul of whatever UB nonsense the gcc/clang people have decided to pull this week;

- Simple interop with foreign APIs (of course, reading is very easy; and my implementation explicitly allows the capacity to be smaller than the size in order to allow creation of one)

Not very convenient to pass these things to a function, though. I also don't like that the ARRAY macro generates 3 declarations. These don't really seem to be problems in practice however.

This might not be to all tastes, but there you go.

(Here's another option, that's more like the link here: https://github.com/nothings/stb/blob/master/stretchy_buffer.... - note note about strict aliasing, which I imagine could apply to the list macros here as well. Obligatory links: http://blog.metaobject.com/2014/04/cc-osmartass.html, http://robertoconcerto.blogspot.co.uk/2010/10/strict-aliasin...)

[+] jevgeni|10 years ago|reply
There's a an error in one of examples:

    List(int*) mylist = NULL; // NULL represents the empty list.
    int a=1, b=2;
    list_append(l, &a);
What's lower case L?
[+] nrj|10 years ago|reply
It seems that `mylist` should be `l`.
[+] Ace17|10 years ago|reply
Why not use ... oh, whatever.