valentin
valentin

Reputation: 175

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?

Upvotes: 2

Views: 3184

Answers (4)

eerorika
eerorika

Reputation: 238361

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.

Upvotes: 8

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14604

But when I want to get the pointer to the array as the pointer to the fixed size array, I must use & symbol.

Here your statement is completely wrong in particular case and half-wrong in general. You already obtained it and assigned to pointer variable parray, then... tried to treat address of that variable as a pointer to array? In particular case name of array decays to pointer to array. In general pointer to array is same as pointer to first element of array, so you can use &array[0] or just array

Upvotes: 0

Sourav Ghosh
Sourav Ghosh

Reputation: 134336

The assignment statement,

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

and later doing

  uint8_t test2 = **p2; 

violates strict aliasing.

To elaborate, &parray is of type uint8_t**, you're casting that to be of type (uint8_t (*)[4]) (they both are not of compatible type) and you try to dereference the target pointer. This causes undefined behavior.

Related, C11, chapter §6.5/P7

An object shall have its stored value accessed only by an lvalue expression that has one of the following types: 88)

— a type compatible with the effective type of the object,

— a qualified version of a type compatible with the effective type of the object,

— a type that is the signed or unsigned type corresponding to the effective type of the object,

— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

— a character type.

Upvotes: 2

Caleth
Caleth

Reputation: 62719

"parray is obviously nearly the same as array" <- this part is incorrect

There is an implicit conversion from the type of array to the type of parray, such that initialising (or assigning), e.g. uint8_t* parray = array; sets parray to be equal to &array[0]. The conversion doesn't exist in the opposite direction.

In your initialisation of p1 p2 p3, you are masking the types of your expressions with your casts

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

Here the cast is superfluous, &array is already a (uint8_t (*)[4])

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

Here the cast is a lie, &parray is a uint8_t**

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

Here the cast is safe only because of the value of parray

Upvotes: 2

Related Questions