Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This gets messy in a couple of ways; for example, what if you want to pass two of them to a function? Then the names of the parameters generated by the ARRAY_ARG macro will clash and you'll have to add a counter to it, etc. (Also I'm not sure that you can concatenate `* p` with `_length` in `S##_length` where `S` is `* p`, and the same thing for the other, but I understand what you meant.) You'll also have potentially very confusing errors for the users of your library when they happen to create a variable whose name collides with one that the macro generates. And those are just the cursory observations.


ARRAY_PARAM (as I assume you mean?) has no problem with two arrays. You just give them different names. Suppose you do this:

    void CopyIntArray(ARRAY_PARAMS(int,*dest),ARRAY_PARAMS(int,src))
Now you end up with this:

    void CopyIntArray(int **dest,size_t *dest_length,size_t *dest_capacity,
                      int *src,size_t src_length,size_t src_capacity);
And you can call it like this:

    ARRAY(int,xs);
    ARRAY(int,ys);
    CopyIntArray(ARRAY_ARG(&xs),ARRAY_ARG(ys))
Your point about token pasting with * p is a very good one, and I don't think that had occurred to me... but neither clang, gcc or VC++ seems to mind (and I used a number of different versions of each). I need to go and look up what the C standard has to say about this now.

I do note that I didn't use ARRAY_PARAM or ARRAY_ARG all that much in my code, though - but I don't remember whether this is because I found some problem with them in practice, or whether it just ended up that way.

(I'm on OS X right now and I just tried my code with clang. Probably-relevant compile flags were "-std=c1x -Wall -Wuninitialized -Winit-self -pedantic -Werror=implicit-function-declaration -Wsign-conversion -Wunused-result -Werror=incompatible-pointer-types -Werror=int-conversion -Werror=return-type -Wno-overlength-strings -Wunused-parameter".)


Ah good catch, I misread it.


This is what the macros expand to.

From:

    $ cat test.c
    #define ARRAY(T,S) T S;size_t S##_length;size_t S##_capacity
    #define ARRAY_INIT(S)   \
            do {                \
                S=NULL;         \
                S##_length=0;   \
                S##_capacity=0; \
            } while(0)

    #define ARRAY_DESTROY(S) \
            do {                 \
                Array_Free(S);   \
                ARRAY_INIT(S);   \
            } while(0)
    #define ARRAY_ADD(S,X)                                 \
            do {                                               \
                if(S##_length>=S##_capacity)                   \
                    S=Array_Grow(S,sizeof &S,&S##_length,&S##_capacity); \
                S[S##_length++]=(X);                           \
            } while(0)


    void Array_Free(void *p) {
    	free(p);
    }

    void *Array_Grow(void *base,size_t stride,size_t *length,size_t *capacity) {
    	*capacity+=*capacity/2;
    	*capacity=MAX(*capacity,MAX(MIN_CAPACITY,*length));
    	return realloc(base,*capacity*stride);
    }

    #define ARRAY_PARAMS(T,S) T *S,size_t S##_length,size_t S##_capacity
    #define ARRAY_ARG(S) S,S##_length,S##_capacity


    void test(ARRAY_ARG(p), ARRAY_ARG(a))
    {
    }

    int main() {
    	ARRAY(int, myarray);
    	ARRAY(int, ourarray);
    	test(ARRAY_ARG(myarray), ARRAY_ARG(ourarray));
    }
To:

    $ gcc -E test.c
    # 1 "test.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 /usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 2
    # 1 "test.c"
    # 22 "test.c"
    void Array_Free(void *p) {
        free(p);
    }

    void *Array_Grow(void *base,size_t stride,size_t *length,size_t *capacity) {
        *capacity+=*capacity/2;
        *capacity=MAX(*capacity,MAX(MIN_CAPACITY,*length));
        return realloc(base,*capacity*stride);
    }

    void test(p,p_length,p_capacity, a,a_length,a_capacity)
    {
    }

    int main() {
        int myarray;size_t myarray_length;size_t myarray_capacity;
        int ourarray;size_t ourarray_length;size_t ourarray_capacity;
        test(myarray,myarray_length,myarray_capacity, ourarray,ourarray_length,ourarray_capacity);
    }

Looks like fairly reasonable code.

Edit: I would like to see it take a type in the Params and a __typeof__ while passing the args to make sure you know what is going where.


Thanks for trying it out - my post was assembled by copying bits out of the (rather gnarlier) code that I actually used, so I'm glad it mostly survived the process ;)

Interesting point about the type checking; my thinking was that the compiler could check they matched, and that this would suffice - and sure enough it worked absolutely fine in practice. But now that I'm made to think about it again, I think it's probably still not quite good enough to be perfect, because you could do this:

    void f(ARRAY_PARAMS(const char *,*xs)) {
        ARRAY_ADD(*xs,"fred");
    }

    ...
    ARRAY(char *,xs);
    ARRAY_INIT(xs);
    f(&xs);
And now you've got a char * that points to a const string. Erm... that's not good!

EDIT: maybe I see what you're getting at with ARRAY_ARG now. The intention is that you use ARRAY_PARAMS to generate the text for the function declaration or definition (that's why it has the type in it), and ARRAY_ARG to generate the text for the code where you pass one to a function so declared (that's why it's just 3 names - they're intended to be expressions, not names for function parameters). That means test should (?) be like this:

    void test(ARRAY_PARAMS(int, p), ARRAY_PARAMS(int, a))
    {
    }

Hopefully that makes sense.

Maybe they'd have been better off with the common C terminology of formal and actual parameters. Then you'd have ARRAY_FORMAL_PARAMS for ARRAY_PARAMS, and ARRAY_ACTUAL_PARAMS for ARRAY_ARG.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: