0

I stumbled into a weirdness today with the C language that I cannot understand the reasoning behind.

If i have a function like this (assume 32 bit architecture):

void printSize(char array[6]) {
    printf("%zd\n", sizeof array);
}

I will get back 4, which is the size of the pointer. I was expecting to get 6, which is the explicitly declared size in the function prototype.

I get that arrays are passed by reference, and that the underlying type is a pointer. I was assuming that putting the length of the array in the prototype would provide the compiler with the information it needed to return 6.

why does C do this? Also, what is the point of putting a size in a prototype if the compiler can't even do sizeof(), returning that size?

7
  • "Array decay" or some such might be a useful search term. Commented Dec 19, 2019 at 17:58
  • The only real point of putting the size in the parameter is for self-documentation. Array parameters decay to pointers, so the size is irrelevant as far as the compiler is concerned. Commented Dec 19, 2019 at 18:00
  • @PaulR What about e.g. void printSize(char (*array)[6]); versus void printSize(char (*array)[]);? Commented Dec 19, 2019 at 18:03
  • @PaulR I know about this decay, but I was assuming putting the size in the prototype would restore the length information to the compiler for the scope of the function. my question is why the compiler does not do this. They could have made it work this way, but chose not to for some reason Commented Dec 19, 2019 at 18:05
  • @FredLarson It does not, read the whole thing though... I understand why they decay to pointers. I do not understand why the design choice was made to ignore the size information that is provided in the declaration/prototype? Did they think it was unsafe since the calling code has the option not to actually honor the size information? Commented Dec 19, 2019 at 18:14

2 Answers 2

1

The behavior you've observed is mandated by the C standard. Section 6.7.6.3p7 regarding "Function declarators" states:

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. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

So the reason compilers do this is because the standard says they must. Also, from a pragmatic standpoint, it would mean you couldn't pass an actual pointer to such a function. Consider if an array was passed to a function whose parameter type was int * and then passed to a function whose paameter type was int [5], or an array that was allocated dynamically i.e. int *arr = malloc(5 * sizeof(int));

Also, note that this only applies to the first dimension of a multidimensional array. That means that this:

void foo(int arr[4][5])

is the same as

void foo(int (*arr)[5])

But not:

void foo(int **arr)
Sign up to request clarification or add additional context in comments.

5 Comments

"So the reason compilers do this is because the standard says they must." my question is why was the standard written this way? it seems like an odd design choice to make the compiler ignore the supplied size information
@StephenKlein: the behaviour was mandated years before there were prototypes in C. In other words, it is "hysterical raisins" yet again — or "historical reasons" if you prefer. Breaking existing code was something that the C standards committee (correctly) struggled to avoid — without that dedication to preserving existing code as working code, the C90 standard would not have been the success that it was.
@JonathanLeffler thanks for your answer! I was going to re ask my question since I was not very satisfied... Not breaking backwards compatibility makes sense to me, as adding 'un-decaying' in this one case would change the behavior of some older programs with newer compilers. Obvious followup question that I just realized is super important: is C code written with older standards always forwards compatible? or another way: any c90 code will compile with a c99 compiler with the same logical program?
No — things are not perfectly forwards compatible, @StephenKlein. For example, C99 outlawed implicit function declarations and 'implicit int'. Old code might have written main(argc, argv) char **argv; { … }. C99 outlawed that. It required either (preferably) int main(int argc, char **argv) { … } or (decidedly non-preferred) int main(argc, argv) int argc; char **argv; { … }. Both those are C99 compliant, though the non-prototype notation is 'obsolescent' notation. Compilers are often more lax than the standard and continue to allow older notations unless you demand they conform.
@JonathanLeffler Interesting! thanks again :-)
1

Arrays are not "passed by reference". Nothing in C is "passed by reference". Everything is passed by value.

Arrays, when "passed" to functions as arguments, decay to a pointer to the first element of the array.

sizeof array therefore returns 4 (bytes), since that's how large pointers are on your implementation.


The feature which allows you to declare the size of an array parameter is mainly used for documentation. If you see a function prototype such as e.g. void printSize(char array[6]);, you can assume that the function will only be accessing the first 6 elements of the array, though the function doesn't have to strictly abide by this (nor will the compiler complain if it doesn't).

6 Comments

"Arrays are not "passed by reference". Nothing in C is "passed by reference". Everything is passed by value. " I don't like that you are trying to haggle over semantics here. Passing an array into a function call in C passes what can be thought of as the reference to the array. "The feature which allows you to declare the size of an array parameter is mainly used for documentation. " not much of a feature! Your answer did not address the 'why' of the issue which is the whole point
The last statement doesn't make much sense to me.
@machine_1 The one in the parenthesis? I tried to clear it up.
@StephenKlein Think about this example: int x = 42; int *p = &x; someFunc(p); Here, you passed a value to a function. That value was a reference to an integer, but it was still a value (an address in the form of a 4-byte number).
@bool3max I understand your example. My quesion is this: why can't the compiler take the value of the pointer to the first element of the array, and the explicit size given in the function definition, and then treat it as if it was declared as an array of that size within the function? just seems weird to me.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.