6 ms·
This sort of approach is a pain to use, because you keep having to cast when you're in the debugger, and there's zero type safety. And I'm afraid I don't have m
by to3m 10y ago
This sort of approach is a pain to use, because you keep having to cast when you're in the debugger, and there's zero type safety. And I'm afraid I don't have much positive to say about something like "((Vector2i * )arr3->data)[0].x = 333".
You can do better than this!
What is an array? It's 3 variables: base, length and capacity. So why not decide that an array is just that. 3 variables of the right size and type.
#define ARRAY(T,S) T S;size_t S##_length;size_t S##_capacity
Then you can make one like this:
ARRAY(int,xs);
You'll also need to initialise and these destroy array "objects".
#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)
Add you'll probably want to add an item to an array too.
#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)
So you might use them like this:
ARRAY(int,xs);
ARRAY_INIT(xs);
for(int i=0;i<100;++i)
ARRAY_ADD(xs,i);
ARRAY_DESTROY(xs);
Array_Free is very simple, and Array_Grow is barely more complicated (however I wrote it off the cuff, so of course it could still be wrong). Both of these mainly exist just to keep stdlib.h out of the header.
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);
}
Array accesses and iteration and the like are just done in the traditional way:
for(size_t i=0;i<xs_length;++i) {
printf("%d\n",xs[i]);
}
Even performs nicely with -O0.
For a full implementation you'll probably also need a way of generating a static array. (I mainly found myself needing this for test code, which uses globals for convenience; most arrays I create normally are locals, or parts of structs.)
You'll also need a parameters list for use in a function declaration or definition, and a macro that expands to all 3 variables.
#define ARRAY_PARAMS(T,S) T *S,size_t S##_length,size_t S##_capacity
#define ARRAY_ARG(S) S,S##_length,S##_capacity
Like then you might have a function that takes a pointer to an "array":
void FunctionThatTakesAnArray(ARRAY_PARAMS(T,*p));
And you call it like this:
ARRAY(T,myarray);
FunctionThatTakesAnArray(ARRAY_ARG(&myarray));
(I found this cropped up often enough that I needed the macro, but it was less common than I thought.)
There's more you can do, but the above is the long and the short of it.
This might all look terrible - or perhaps it sort of looks OK, but you're just not sure that it would actually work - but I've used this in a prototype project and thought it worked out well. (I've been using C for 20+ years, so hopefully even if I've got no taste, I've at least got a rough feel for what works out OK and what's going to end up a disaster.)
- tines 10y agoThis 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.
- gravypod 10y agoThis 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.