Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointer to fixed size array behaviour

Why does this work as it does?

uint8_t array[4] = {1,2,3,4};
uint8_t* parray = array;
uint8_t (*p1)[4]  = (uint8_t (*)[4])&array;
uint8_t (*p2)[4]  = (uint8_t (*)[4])&parray;
uint8_t (*p3)[4]  = (uint8_t (*)[4])parray;
uint8_t test1 = **p1;    // test1 = 1
uint8_t test2 = **p2;    // test2 = something random
uint8_t test3 = **p3;    // test3 = 1

parray is obviously nearly the same as array. For example, array[0] == parray[0]. But when I want to get the pointer to the array as the pointer to the fixed size array, I must use & symbol. When I want to get the pointer to the parray, I must not.

Practical example.

There is the function that accepts a pointer to the fixed size array

void foo(uint8_t (*param)[4])
{
    ...
}

When I get the param in another function as a pointer, can I pass it to foo this way?

void bar(uint8_t param*)
{
    uint8_t (*p)[4]  = (uint8_t (*)[4])param;
    foo(p);
}

Is there a better way?

like image 436
valentin Avatar asked Sep 10 '26 15:09

valentin


1 Answers

This is a feature called array decaying. The array variable is said to decay into a pointer to first element, when the variable name is used in a value context.

Here the array is used in a value context: parray = array, so it decays. You could write the decay explicitly: parray = &(array[0]). Former (implicit decay) is just syntactic sugar for the latter.

Operand of the addressof operator is not a value context. As such, the array name does not decay.&array is different from &(array[0]). First takes the address of an array type, the latter takes an address of the element type. parray on the other hand is a completely different variable, and &parray returns the address where the pointer is stored, which is not the address where the array is stored.

uint8_t (*p1)[4]  = (uint8_t (*)[4])&array;

This is correct, although the conversion is redundant because &array is already of type uint8_t (*)[4].

uint8_t (*p2)[4]  = (uint8_t (*)[4])&parray;

This is wrong. parray is of type uint8_t* and the address where it is stored doesn't contain an object of type uint8_t[4]. Instead it contains the pointer.

uint8_t (*p3)[4]  = (uint8_t (*)[4])parray;

This is a bit dubious. parray is a pointer to uint8_t, not a pointer to uint8_t[4]. However, it happens to point to an address that also contains a uint8_t[4] object, so this works.


parray is obviously nearly the same as array

But clearly not exactly same, as evidenced by the behaviour of your program.

array is an array of four uint8_t elements, and parray is a pointer to uint8_t, which points to the first element of array. This distinction is important to understand.


Conclusion: It is important to understand what array decaying is, and what is the difference between an array and a pointer, and most importantly: Explicit conversions can hide mistakes from the compiler - avoid them when you can.


For the edit:

When I get the param in another function as a pointer, can I pass it to foo this way?

Only if you can prove that param points to the first element of a uint8_t[4]. That is essentially a pre-condition of bar.

However, it is better to not rely on verbal pre-conditions, when you could use the type system to communicate the requirements:

Is there a better way?

Change the parameter type of bar, so that users know to pass a pointer of correct type:

void bar(uint8_t (*param)[4]) {
    foo(param);
}

Of course, this makes bar redundant in this simple example.

like image 113
eerorika Avatar answered Sep 13 '26 04:09

eerorika