1
votes

Let's assume that I have a struct with a void ** member. This member serves as an array of pointers to data channels. The data type is irrelevant. Below is an example of how I'd like to allocate memory for this 2D array and then associate channel pointers with their memory.

#define N_CHANNELS (/*Number of data channels*/)
#define N_SAMPLES (/*Number of data samples per channel*/)
typedef /*Some data type*/ DATA_TYPE;

void **ppdata; /*Conceptual struct member*/

/*Allocate memory for array of data channels */
ppdata = (void **) malloc(sizeof(DATA_TYPE *) * N_CHANNELS);
ppdata[0] = malloc(sizeof(DATA_TYPE) * N_CHANNELS * N_SAMPLES);

/*Cast for pointer arithmetic and data access*/
DATA_TYPE **ptr = (DATA_TYPE **) ppdata;

/*Associate channels with their memory*/
int alloc_index;
for (alloc_index = 0; alloc_index < N_CHANNELS; alloc_index++)
{
     ptr[alloc_index] = (*ptr + alloc_index * N_SAMPLES);
}

So, the question arises: Is this dereference and allocation behaving as I have assumed it is?

ppdata[0] = malloc(sizeof(DATA_TYPE) * N_CHANNELS * N_SAMPLES);

i.e., is this allocation compatible with the manner in which I later access the memory?

DATA_TYPE **ptr = (DATA_TYPE **) ppdata;
...
ptr[alloc_index] = (*ptr + alloc_index * N_SAMPLES);
2
@4386427 I've allocated the memory for all channels w/ that second malloc() statement because I need the data for all channels to be in contiguous memory. I assign ppdata[1], ..., ppdata[N_CHANNELS -1] with their appropriate addresses in the for loop. - sgoldburg
typedef DATA_TYPE; is typedef int DATA_TYPE; This is determined at compile time. - BLUEPIXY
@BLUEPIXY The typedef is just for the purpose of illustration. In an application of this idea the data type would be delivered another way. - sgoldburg
Although void * is the "generic" pointer type in C, it turns out that void ** can not portably be used as a generic pointer-to-pointer. So your line DATA_TYPE **ptr = (DATA_TYPE **)ppdata is suspect. See c-faq.com/ptrs/genericpp.html - Steve Summit
@4386427 The purpose of the loop is to initialize, not dereference. - sgoldburg

2 Answers

0
votes

Your general idea looks OK to me, but (void **) malloc(sizeof(DATA_TYPE *) * N_CHANNELS) makes my spider-sense tingle. You allocate space for an array of N_CHANNELS pointers to DATA_TYPE, but treat it as an array of pointers to void. There's nothing in C that guarantees that a void * and a DATA_TYPE * have the same size and representation (unless DATA_TYPE happens to be char). So your subsequent assignment to ppdata[0] = ... might produce garbage when looked at as the bits of a DATA_TYPE * (which is what you do later through ptr).

I'd much prefer it this way:

/* overflow check omitted */
DATA_TYPE *storage = malloc(N_CHANNELS * N_SAMPLES * sizeof *storage);
/* error check omitted */

/* overflow check omitted */
DATA_TYPE **pointers = malloc(N_CHANNELS * sizeof *pointers);
/* error check omitted */

for (size_t i = 0; i < N_CHANNELS; i++) {
    pointers[i] = storage + i * N_SAMPLES;
}

/* struct member */
void *pdata = pointers;

Then later on you can recover your array of pointers by doing:

DATA_TYPE **pointers = pdata;
do_stuff_with(pointers[i][j]);

Technically you don't need a separate array of pointers, because you have a nice rectangular array. You could just do:

/* overflow check omitted */
DATA_TYPE *storage = malloc(N_CHANNELS * N_SAMPLES * sizeof *storage);
/* error check omitted */

/* struct member */
void *pdata = storage;

And later on:

DATA_TYPE *storage = pdata;
do_stuff_with(storage[i * N_SAMPLES + j]);

I.e. just manually calculate the index from i and j.

0
votes

C does not support the ability to discover datatype at runtime. You are going to have to pass in a variable that defines the number of bytes of the datatype you are going to be using if you want this to work with generic datatypes.