Reputation: 2802
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
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
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 int
s 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
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 char
s, 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]
dataArray[0] + 1
*(dataArray[0] + 1)
dataArray[0][1]
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