SlyGuy
SlyGuy

Reputation: 117

Passing an array and its length with a struct in C weird behavior

I'm trying to return an array and its length from one function to another. I've learned that I can't simply determine the size of the returning array in the parent function with sizeof() because arrays are reduced to pointers when passed as parameters. I've figured out that I can use a struct to return the array pointer and size to the parent function, so that's what I'm attempting.

The array is of type unsigned char* since I'm working with memory addresses. The array appears fine before it is sent to the struct. Once I try to reference the array via the struct in a parent function, all of the memory addresses are split up. I have a hunch that struct padding could be the problem.

I've written some prints to really analyze what's going on.

struct arrayPasser{
    int length;
    unsigned char *arr;
};

unsigned char *arr[arrLength]; /*array of pointers*/

/*filling out the array with addresses*/

struct arrayPasser pass;

pass.length=arrLength;
pass.arr=&arr;

printf("pass.arr: %#x\n", pass.arr); /*print array location*/

printf("*(&arr): %#x\n", *(&arr));
printf("**(&arr): %#x\n", **(&arr));

/*original array*/
printf("arr[0] %#x\n", arr[0]);     
printf("arr[1] %#x\n", arr[1]);
printf("arr[2] %#x\n", arr[2];

/*referencing array through struct*/
printf("0 offset: %#x\n", *pass.arr);
printf("1 offset: %#x\n", *(pass.arr+1));
printf("2 offset: %#x\n", *(pass.arr+2));
printf("3 offset: %#x\n", *(pass.arr+3);
printf("4 offset: %#x\n", *(pass.arr+4));
printf("5 offset: %#x\n", *(pass.arr+5));
printf("6 offset: %#x\n", *(pass.arr+6));
printf("7 offset: %#x\n", *(pass.arr+7));

printf("pass.arr[0]: %#x\n", pass.arr[0]);
printf("pass.arr[1]: %#x\n", pass.arr[1]);
printf("pass.arr[2]: %#x\n", pass.arr[2]);
printf("pass.arr[3]: %#x\n", pass.arr[3]);
printf("pass.arr[4]: %#x\n", pass.arr[4]);
printf("pass.arr[5]: %#x\n", pass.arr[5]);
printf("pass.arr[6]: %#x\n", pass.arr[6]);
printf("pass.arr[7]: %#x\n", pass.arr[7]);

Output (notice how arr[0] is a full 32-bit address but pass.arr[0] is only one byte of the 32-bit address) :

arr[0] 0x4affb000
arr[1] 0x4affd000
arr[2] 0x4affc000
pass.arr: 0x58dcf10
*(&arr): 0x58dcf10
**(&arr): 0x4affb000
0 offset: 0
1 offset: 0xb0
2 offset: 0xff
3 offset: 0x4a
4 offset: 0
5 offset: 0xd0
6 offset: 0xff
7 offset: 0x4a
pass.arr[0]: 0
pass.arr[1]: 0xb0
pass.arr[2]: 0xff
pass.arr[3]: 0x4a
pass.arr[4]: 0
pass.arr[5]: 0xd0
pass.arr[6]: 0xff
pass.arr[7]: 0x4a

Upvotes: 0

Views: 250

Answers (2)

Dmitry
Dmitry

Reputation: 2015

unsigned char *arr[arrLength] is an array of pointers, but since arrays are essentially pointers, the variable arr points to the first pointer in the array, so it is an equivalent to unsigned char **arr.

But unsigned char *arr is a pointer to char, but not a pointer to a pointer. In other words, it is array of char, but not array of pointers.

Declare your structure as:

struct arrayPasser{
    int length;
    unsigned char **arr;
};

And you will get a pointer instead of char when you dereference the arr.

Let me try to explain why your code doesn't work. Consider this array:

unsigned char *arr[3]
arr[0] = 0x4affb000;
arr[1] = 0x4affd000;
arr[2] = 0x4affc000;

When you do that, the data you entered placed in the memory like this:

00 b0 ff 4a   00 d0 ff 4a   00 c0 ff 4a
^             ^             ^
|             |             |
arr           arr + 1       arr + 2
points here   points here   points here

The integers stored in the memory have little endian byte order, so their bytes are reversed.

When you do arr[0] you get 0x4affb000 because sizeof(arr[0]) is 4, hence you get 4 bytes.

Then, you do this:

unsigned char *arr2 = arr;  /* which is equal to arr2 = &arr */

The memory isn't changed, but the way the pointer behaves is changed. Now it is a pointer to char, not a pointer to pointer, so the size of an object referenced by the pointer is now 1 instead of 4. In other words, both arr and arr2 point to the same memory location, but size of an element of arr is 4 and size of an element of arr2 is 1.

Here is what you get for arr2:

00 b0 ff 4a   00 d0 ff 4a   00 c0 ff 4a
^  ^  ^  ^
|  |  |  +--arr2 + 3
|  |  +--arr2 + 2
|  +--arr2 + 1
+--arr2

Now when you do arr2[0] you get 0x00 because sizeof(arr2[0]) is 1 byte.

Upvotes: 2

Nisse Engström
Nisse Engström

Reputation: 4751

(Apparently, the output is from a different program than the one you posted, but never mind.)

pass.length=arrLength;
pass.arr=&arr;

You are trying to convert a pointer to array to a pointer to unsigned char. The two types are not compatible, so you're going to need to cast &arr to the right type. But that's not what you want. See below.

printf("pass.arr: %#x\n", pass.arr); /*print array location*/

Note: The %x conversion specifier expects a parameter of type unsigned int. You should cast pass.arr to unsigned int or cast it to (void *) and print it using the %p specifier.

printf("*(&arr): %#x\n", *(&arr));

Passing arr to printf(). This is a pointer to the first element of the arr array.

printf("**(&arr): %#x\n", **(&arr));

Passing *arr to printf(). This is the first pointer stored in the arr array, eg. arr[0].

printf("arr[0] %#x\n", arr[0]);     
printf("arr[1] %#x\n", arr[1]);
printf("arr[2] %#x\n", arr[2]); /* Added missing ')'. */

Passing the first three pointers stored in arr to printf().

printf("0 offset: %#x\n", *pass.arr);
printf("1 offset: %#x\n", *(pass.arr+1));
printf("2 offset: %#x\n", *(pass.arr+2));
printf("3 offset: %#x\n", *(pass.arr+3);

Passing the first four elements pointed to by pass.arr to printf(). The elements pointed to by pass.arr are of type unsigned char, not unsigned char *. Since the unsigned char is passed to a variadic function, it will be promoted to int (or in obscure cases unsigned int) which will in all probability be printed just fine.

The reason the addresses appear to be split up is that the contents of arr (pointers) are now being reinterpreted as an array of characters through pass.arr.

printf("pass.arr[0]: %#x\n", pass.arr[0]);
printf("pass.arr[1]: %#x\n", pass.arr[1]);
printf("pass.arr[2]: %#x\n", pass.arr[2]);
printf("pass.arr[3]: %#x\n", pass.arr[3]);

Exactly the same as above. The A[B] notation is (mostly) equivalent to *((A)+(B)).

If you want to return a pointer to arr through your struct arrayPasser, you need to pass a pointer to pointer, not just a pointer. Change your struct to:

struct arrayPasser{
    int length;
    unsigned char **arr;
};

And assign a pointer to the first element of arr to it:

pass.arr = arr;

As I explained above, you can't assign &arr to pass.arr, even if the pointer value happens to be exactly the same. The two pointers have entirely different types.

Apparently you didn't think this subject was confusing enough, so you went right ahead and gave two different identifiers the same name. :-)

Upvotes: 1

Related Questions