Oskar Enoksson
Oskar Enoksson

Reputation: 69

C array/pointer declaration syntax quiz

I thought I understood how C arrays, pointers and pointers to arrays worked, but now I stumbled upon a piece of working code which I don't understand:

int sum(const int * const buf, int len)
{
  int result = 0;
  const int (*p)[];
  int n;

  p = (const int(*)[]) buf;
  // p = buf without cast gives compiler warning here (but works)
  // p = (const int(*)[]) &buf;  // Doesn't work! Segfault!

  for( n=0; n<len; n++)
  {
    result += (*p)[n];
  }
  return result;
}

This actually works. But how? The question is: how does that assignment from "buf" to "p" work? I thought this declaration of p was similar to "pointer to pointer to int", so I would have expected a "&" necessesary at the assignment. Why is this not necessary?

In fact the following prototype also works (with the same function body above):

int sum(const int buf[const], int len)

Now it seems even more clear that the declaration of "p" adds one level of pointer re-direction compared to the declaration of "buf". Still ... the assignment works fine without any "&" necessary. Can someone explain it?

Upvotes: 0

Views: 196

Answers (1)

Lundin
Lundin

Reputation: 213940

First of all, please note that this code is not good practice. The pointer conversion is fishy-looking and unusual, and in particular it casts away const qualifiers, which is bad practice. (Declaring a parameter as ...* const buf is pretty fishy to begin with though.)

This actually works. But how?

There is a pointer conversion from (qualified) int pointer to int array pointer. Given that these two pointer types do not have different representation or different alignment (highly unlikely but theoretically possible), the pointer conversion in itself is ok (as per C11 6.3.2.3/8).

What matters then is the effective type of the data, which is an (array of) int. Effective type is a formal C term used for determining what type that is actually stored at a location, no matter the pointer types used for accessing. As long as the data is accessed through a pointer type that is compatible with the effective type of what's stored there, the code should work fine.

The formal reason why this is fine is C11 6.5/7 ("the strict aliasing rule"):

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
/--/
— an aggregate or union type that includes one of the aforementioned types among its members

Where "aggregate" is C standard gibberish for arrays and structs. If we access the data through an expression of array type, where the array element type is a type/qualified type compatible with the actual type, int in this case, everything is fine.

As for how it works, it simply de-references an array pointer. p = (const int(*)[]) buf; says "threat what's stored in this variable as a pointer to array of integer (and qualifiers be damned)". Then (*p)[n] takes the array pointer, de-references it to get the actual array, then uses the array index.

p = buf without cast gives compiler warning here

If you compile with gcc -pedantic-errors you get a more correct error. The pointer types are not compatible and so the compiler must generate a diagnostic message here - since p = buf is not valid C.

p = (const int(*)[]) &buf; // Doesn't work! Segfault!

This is because what's stored at &buf is not an array of int, it is just the pointer buf itself, likely allocated on the stack as a parameter to your function.

Upvotes: 2

Related Questions