5

Consider this code snippet:

void foo(int a[], int b[]){
    static_assert(sizeof(a) == sizeof(int*));
    static_assert(sizeof(b) == sizeof(int*));
    b = a;
    printf("%d", b[1]);
    assert(a == b); // This also works!
}

int a[3] = {[1] = 2}, b[1];
foo(a, b);

Output (no compilation error):

2

I can't get the point why b = a is valid. Even though arrays may decay to pointers, shouldn't they decay to const pointers (T * const)?

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
iBug
  • 35,554
  • 7
  • 89
  • 134

3 Answers3

10

They can't.

Arrays cannot be assigned to. There are no arrays in the foo function. The syntax int a[] in a function parameter list means to declare that a has type "pointer to int". The behaviour is exactly the same as if the code were void foo(int *a, int *b). (C11 6.7.6.3/7)

It is valid to assign one pointer to another. The result is that both pointers point to the same location.


Even though arrays may decay to pointers, shouldn't they decay to const pointers (T * const)?

The pointer that results from array "decay" is an rvalue. The const qualifier is only meaningful for lvalues (C11 6.7.3/4). (The term "decay" refers to conversion of the argument, not the adjustment of the parameter).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
M.M
  • 138,810
  • 21
  • 208
  • 365
  • @iBug C11 6.7.6.3/7 – M.M Nov 24 '17 at 07:26
  • Your explanation about why the pointers aren't const is specifically good. – iBug Nov 24 '17 at 07:44
  • @iBug [language-lawyer] is not a shorthand for "Please provide a quote from the standard", it's a tag for questions specifically about the spec itself. – Baum mit Augen Nov 24 '17 at 12:25
  • 1
    Although arrays are converted to pointers, the function parameters are adjusted (not converted) to pointers. It would have been reasonable for the C standard to say that `int a[]` is adjusted to `int * const a`, as this may better capture the intent of declaring `a` like an array—you cannot change an array, so let‘s make the parameter unchangeable too. So the answer here is not a consequence of rvalues but of a choice made by the standard, likey affected by history (since `const` is new). – Eric Postpischil Nov 24 '17 at 13:12
  • @EricPostpischil IMO that would just muddy the waters. – M.M Nov 26 '17 at 06:51
8

Quoting C11, chapter §6.7.6.3, Function declarators (including prototypes)

A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. [...]

So, a and b are actually pointers, not arrays.

There's no assignment to any array type happennning here, hence there's no problem with the code.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • So is it compiled as if they were `int*` even not written as-is? – iBug Nov 24 '17 at 07:27
  • @iBug that's the idea, yes, – Sourav Ghosh Nov 24 '17 at 07:27
  • 2
    [Holy crap, you really can put type qualifiers between the brackets.](https://ideone.com/J4IoZT) That is a really weird syntax. – user2357112 Nov 24 '17 at 07:29
  • 2
    I'm amazed that people are still amazed by features added to the language 18 years ago... in computing that's a long time to go without brushing up on your knowledge! – M.M Nov 24 '17 at 07:34
  • @M.M: It's not exactly one of the more exciting C99 features, so while I've long since read up on C99, this change never actually came up in any "what's new in C99" reference I saw. – user2357112 Nov 24 '17 at 07:59
  • @user2357112: You think putting type qualifiers between brackets is weird? Compile and run this using standard C11: `#include ` / `void foo(char x[][printf("Hello, world.\n")]){} int main(void) {foo(0);}`. – Eric Postpischil Nov 24 '17 at 15:09
4

Yes, it would have made sense for array parameters declared with [] to be adjusted to const-qualified pointers. However, const did not exist when this behavior was established.

When the C language was being developed, it made sense to pass an array by passing its address, or, more specifically, the address of the first element. You certainly did not want to copy the entire array to pass it. Passing the address was an easy way to make the array known to the called function. (The semantics for the reference types we see in C++ had not been invented yet.) To make that easy for programmers, so that they could write foo(ArrayA, ArrayB) instead of foo(&Array[0], &ArrayB[0]), the mechanism of converting an array to a pointer to its first element was invented. (Per M.M. and The Development of the C Language by Dennis M. Ritchie, this notation for parameters already existed in C’s predecessor language, B.)

That is fine, you have hidden the conversion. But that is only where the function is called. In the called routine, the programmer who is thinking about passing an array is going to write void foo(int ArrayA[], int ArrayB[]). But since we are actually passing pointers, not arrays, these need to be changed to int *ArrayA and int *ArrayB. So the notion that parameters declared as arrays are automatically adjusted to pointers was created.

As you observe, this leaves the programmer able to assign values to the parameters, which changes the apparent base address of the array. It would have made sense for a parameter declared as int ArrayA[] to be adjusted to int * const ArrayA, so that the value of the parameter ArrayA could not be changed. Then it would act more like an array, whose address also cannot be changed, so this better fits the goal of pretending to pass arrays even though we are passing addresses.

However, at the time, const did not exist, so this was not possible, and nobody thought of inventing const at that time (or at least did work on it enough to get it adopted into the language).

Now there is a large amount of source code in the world that works with the non-const adjustment. Changing the specification of the C language now would cause problems with the existing code.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Re. your second paragraph: The syntax `foo(ArrayA, ArrayB);` already existed in the B language , in which an array really was a pointer . Declaring an array reserved space for both the pointer, and the element storage, and passing the array meant passing the pointer. The C language changed the semantic of an array (i.e. not reserving space for the pointer) and simultaneously added the decay rule so that existing B code would continue to work. [Further reading](http://csapp.cs.cmu.edu/3e/docs/chistory.html) – M.M Nov 26 '17 at 20:40