Dynamic array implementation
Some good practices:

Assert on NULL parameter

Whenever you have a function passing an array you should either do a runtime check to see if it's NULL or do an assert(array). To catch bugs where a NULL array is passed to the functions.

Check return values for alloc functions

You should always make sure you get a non-NULL result from any allocation, malloc, realloc and such to fail gracefully, or at least exit(1) with a nice out of memory message. Good for catching bugs where accidentally an uninitialized count value is passed to an allocating function.

Check for negative input

Since for instance your numElements is an int instead of size_t you should either change it, or at least check for negative values.

Deleting many values

I don't know how common a use case this would be, but since on each delete, you might move a large chunk of values. Adding a function that either takes a list of indices to delete and do it in one go, by simply refilling the array with those indices excluded. Otherwise iterating over and deleting values can be a huge operation depending on the size of the array.

Destroy function

I agree with this comment:

/* Probably useless macro */
#define array_free(ARRAY) free((ARRAY)-&gt;data)


This should be a function that both resets the numElements = 0, capacity = 0 and does the free(array-&gt;data) as well as array-&gt;data = NULL.

This ensures that any reuse of the array will still result in a reallocation and hence not writing to freed memory. However please note that array_add in this case must check that capacity &gt; 0 and at least set it to 1, otherwise the realloc will be 0.