meawoppl
meawoppl

Reputation: 2802

cArray[][] Pointer Array Casting

I am having trouble with a vendor API for retrieving data from a device. A working version of the call as suggested in the vendor docs looks something like the following:

int dataArray[15][20];
getData(15, 20, (int*) dataArray);

The call signature is described as:

void getData(xSize, ySize, int*);

I am interested in having this function to copy data into a contiguous array of ints with a dynamic size, with something like the following:

int *flatDataArray = (int*) malloc(xSize * ySize * sizeof(int));

I am having a hard time getting the call right, and segfaults are somewhat difficult to debug :(

I think my misunderstanding is around how a int array is represented, and what happens when you cast it to (int*). In this example is "dataArray" of type int**, or something else? If so, would you have to construct an array of pointers into flatDataArray for the above function to work properly?

Upvotes: 1

Views: 259

Answers (3)

WhozCraig
WhozCraig

Reputation: 66234

The first line "works" because of the cast.

Given:

int dataArray[15][20];
getData(15, 20, (int*) dataArray);

The expression value type of dataArray stand-alone is int (*)[20]. a pointer to an array of 20 int. This makes sense because per the C standard the expression value of an array is the address of its first element and a pointer-to-type of said-same. Well, the first "element" of an array of arrays (which is what dataArray is) is of type int[20], and the pointer-to-type give us int(*)[20].

That said, the same rules apply to the first array in that array of arrays. In short, this will work:

int dataArray[15][20];
getData(15, 20, dataArray[0]);

Just like dataArray is an expression resulting in the address of the first element (an array) of type int (*)[20], the first array in that array likewise has a expression value which is the address of its first element, an int, and the type associated to that address is int * As it happens, this is the first element of the first array of an array of arrays. In the linear memory backdrop of the underlying system this is running on, it all eventually resolves to the same address (where dataArray lays in memory). It is the types associated with that address, depending on how it is contrived, that are different. Regardless of the types, however, the underlying memory backdrop is the same: a contiguous sequence of 300 int. The address returned by all of the following will be the same; only the types are different (and noted in comment)

&dataArray            // int (*)[15][20]
dataArray             // int (*)[20]
dataArray[0]          // int *
&dataArray[0][0]      // int *

and no, these aren't the only combinations. I left at least one out. See if you can figure out what is missing.

Anyway, allocating as you are here:

int *flatDataArray = malloc(xSize * ySize * sizeof(int));

works because you're simply building a xSize by ySize linear backdrop. Because this is C, the following would also work, utilizing the VLA (variable length array) feature of the language:

int (*arr2D)[ySize] = malloc(xSize * sizeof(*arr2D));

assuming xSize is the number of rows, and ySize is the number of columns you seek (I seem get that wrong half the time, which is why I prefer the monikers "row" and "col"). The above allocation says "Allocate me space for xSize number of int[ySize] things.". The benefit in-code is you can address this just as you would a 2D array of arrays (which is exactly what it is):

arr2d[i][j] = value;

for any i in 0..(xsize-1) and j in 0..(ysize-1), just like you would/could as if you declared it normally. This (VLAs) is one thing C does well that C++ does not (but then again, there are containers-o-plenty in C++ that would have solved this problem a different way in the first place, so it isn't really a fair comparison).

Best of luck.

Upvotes: 2

M.M
M.M

Reputation: 141618

This code is fine:

int dataArray[15][20];
getData(15, 20, (int*) dataArray);

and it works because you are viewing the entire object dataArray via a pointer to int; and since dataArray does contain ints, there aren't any aliasing violations.

It would be undefined behaviour to write getData(15, 20, dataArray[0]); because in this case you are using a pointer to only the array of 20 ints which was the first element of dataArray, so you aren't allowed to overflow those 20 ints. (Ref: C99 6.5.6#8)

I am interested in having this function to copy data into a contiguous array of doubles with a dynamic size

int and double are different, so you cannot pass any sort of array of double to this function (as you have described the function anyway; if this function actually takes void * and then outputs data based on some previous function call, that would be different). Unless the API also includes a function that takes a pointer to double, you have to retrieve the ints and then convert them to double. For example,

int array1[xSize][ySize];
getData( (int *)int_array );

double array2[xSize][ySize];
for ( size_t ii = 0; ii < xSize; ++ii )
    for ( size_t jj = 0; jj < ySize; ++jj )
         array2[xSize][ySize] = array1[xSize][ySize];

The last line performs a value conversion from int to double.

In C arrays can have runtime dimensions like this. (Actually in 2011 this was changed to be an optional feature, previously it was standard). If you are using a compiler which no longer supports this sort of array you can use malloc.

If both dimensions are not known at compile-time, then you have to malloc a 1-D array and then work out offsets into it; e.g.

int *array1 = malloc(xSize * ySize * sizeof *array1);
getData(array1);

double *array2 = malloc(xSize * ySize * sizeof *array2);
for (size_t ii = 0; ii < xSize * ySize; ++ii)
    array2[ii] = array1[ii];

// what was previously array1[4][6]
array2[4 * ySize + 6];

NB. void getData(xSize, ySize, int*); is not a valid call signature. A call signature would have data types instead of variable names. You could find the function declaration in the header file you are including to access this API.

Upvotes: 0

Utkan Gezer
Utkan Gezer

Reputation: 3069

For a 2 dimensional array declared as the following:

double dataArray[15][20];

The flattened out version would simply be the following:

dataArray[0];

And if you were to store this address of the first double into a pointer (address variable) to a double:

double * flatDataArray = dataArray[0];

The following loop, for example, accesses every single element in a contiguous manner, thanks to the fact that they already were contiguous and never in multi-dimensions:

for (int i = 0; i < 15 * 20; i++) {
    dataArray[0][i];    // alternatively: flatDataArray[i];

As per the edit: Change all that double to int, or whatever type you wish.

Say that it was of chars, here's how it would look like, and what dataArray, *dataArray and **dataArray would resemble:

   char0  char1  ...  char14  char15  char16  ...  char29  ...  char299
// >                             dataArray                            <
// >     *dataArray        <  >                         <               
// >   <  >   <
//   ^ the **dataArray

The dataArray may not get dereferenced more, twice max. By taking the address of **dataArray, you'll have *dataArray on your hands. Similar for *dataArray. Not for dataArray, in which case you'll rather have simply &dataArray.

Each one of &dataArray, dataArray and *dataArray will have the same value, which is the address of char0, or the array that spans from char0 to char14, or the array of arrays that spans from char0-char14 to char285-char299.

**dataArray will have the value that char0 holds.

What's the difference between the other three? Well, they point to different types, hence incrementing them will have different interpretations. This is similar to how, when you increment an integer pointer, you advance 4 bytes instead of 1.

If you happen to increment &dataArray by 1, like &dataArray + 1, you'd advance 300 bytes (300 * sizeof(char)). If you happen to increment dataArray by 1, which is the address of *dataArray, you'd advance 15 bytes, and so on.

If you happen to increment the *dataArray by 1, which is the address of **dataArray the char0, you'd advance 1 byte. Now, here's what:

  • *dataArray is dataArray[0]
  • incrementing it by 1 is dataArray[0] + 1
  • that dereferenced would be *(dataArray[0] + 1)
  • shorthand for that is dataArray[0][1]
  • say we were to increment it more, say, 15 times in total
  • dataArray[0][15]

Seems illegal, doesn't it? Well, it's not, because that bit of the memory is still within the part allocated for you. In fact, you can go as far as dataArray[0][299]. It just means advance 299 bytes, and access that location, and that location is all yours.

Crazy, isn't it? Even more crazy that I've wrote so much about it. I'm not even sure if this answers your question anymore... I suppose a call like getData(15, 20, dataArray[0]); or getData(15, 20, *dataArray); would therefore be more sensible, although I doubt that a typecast there would fail.

I hope this at least will make sense to someone.

Upvotes: 1

Related Questions