4
votes

The c11 standard says that sizeof,

"when applied to an operand that has array type, the result is the total number of bytes in the array"

  • (6.5.3.4, bullet 4).

The foot note (103) says:

"When applied to a parameter declared to have array or function type, the sizeof operator yields the size of the adjusted (pointer) type".

I take from this that when applied to an array type, sizeof gives the size of the array (number of elements x size of elements), but applied to parameters declared to have array type, it gives the size of the pointer.

My question:

How is it possible to have an object of array type that does not produce the size of a pointer, due to the foot note?

I feel like I cannot trust the sizeof operator in some circumstances without knowing this.

Thanks.

EDIT: I guess I should clarify my concern, if "int a[4]" is defined, then I see from the responses that sizeof a==4*sizeof(int), but what about sizeof(a+0)? It seems that sizeof(a+1) must be evaluated as a pointer. I am concerned with circumstances other than function calls where an array decays to a pointer.

6
Essentially you just have to be aware that arrays decay to pointers when passed as parameters, so don't use sizeof on an array that is passed to you as a parameter.Paul R
It's just restating that void f(char p[]) declares the parameter p to have pointer type.user253751

6 Answers

4
votes

The key point from the quote are "parameter declared to have array type" and "the adjusted (pointer) type". What this is referring to is the fact that a function parameter of "array type" is adjusted to pointer type. Once that adjustment is made, the type is pointer, and its size has to be the size of a pointer. It cannot be anything else. This is how it works:

void foo(int p[42]);

is adjusted to

void foo(int* p);

Those two function declarations are equivalent. So the type of p is int*. and sizeof(int*) is always the size of a pointer.

However, in a different context, there is no type adjustment:

int a[42]; // no adjustment. a is an array of size 42;

sizeof(a); // gives the size of type int[42]

Here, the type of a really is "size 42 array of int". The sizeof operator has access to this (compile-time) information and thus can give the correct size for that type.

Note that this is related to array decay, where an array can "decay" into a pointer to its first element under some circumstances. That decay is what would allow you to call foo with an array argument:

int a[26];
foo(a);     // foo(int*): a decays to int*

int* p = a; // same phenomenon

So, adjustment changes the function signature, and decay allows you pass an array to a function that expects a pointer.

Update Concerning your update, the application of binary arithmetic operators is one of the many cases where an array decays to a pointer to its first element. For example

#include <stdio.h>

int main(void)
{
    int a[42];
    printf("sizeof(a) = %zu\n", sizeof(a));
    printf("sizeof(a+1) = %zu\n", sizeof(a+1));
    return 0;
}

Output:

sizeof(a) = 168
sizeof(a+1) = 8
3
votes

In response to your update (being concerned about sizeof(foo+1) type situations:

Yes, sizeof applied to array_name + int is equivalent to sizeof &(array_name[int]);, on the basis that an array, decays into a pointer in those cases. Likewise, to get to the actual value out of the array you don't write arr_name + 1, but rather *(arr_name + 1).

So, taking the footnote into account, when will a sizeof yield the actual array size (in bytes)? For that, look at what the standard says about arrays decaying into pointers:

Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.

Meaning:

  • Using sizeof directly on the array variable (sizeof array_var)
  • Dereferencing a pointer to an array (sizeof *(&array_var)) Note: This also applies when you pass this pointer to an array to another function, but isn't always the best way to go (see example below)
  • string literals (like the Rvalue in char foo[] = "foobar"; => sizeof("foobar");)

In all other cases (AFAIK), the array decays into a pointer, and sizeof will yield the size of a pointer:

  • Arithmetic on array => pointer arithmetic (sizeof (array_var +1 ))
  • Passing array to function (decays into pointer)
  • ...

passing an array to a function

So using the unary & operator, it is possible to pass a pointer to an array to a function, but it's rarely done. Still, here's an example:

void pointer_to_array(char (*ptr)[]);//pointer to array type
void error_p_to_arr(char (*ptr)[]);

int main ( void )
{
    char str[] = "some random string";//array of 18 bytes
    printf(
        "Sizeof array %zu\n",
        sizeof str
    );
    pointer_to_array(&str);
    return 0;
}
//we need to specify the exact type, including size!
//replace 18 with 10, you're fine, but use 20 and you're in trouble
void pointer_to_array(char (*ptr)[18])
{
    printf(
        "sizeof argument: %zu\nsizeof array %zu",
        sizeof ptr,//4 or 8
        sizeof *ptr//18!! YaY
    );
}
//if we don't specify the array size here
void error_p_to_arr(char (*ptr)[])
{
    printf(
        "sizeof argument: %zu\nsizeof array %zu",
        sizeof ptr,//4 or 8
        sizeof *ptr//ERROR!
    );
}

The latter sizeof *ptr will cause an error ("invalid application of ‘sizeof’ to incomplete type ‘char[]’"). Because this way of passing an array around is quite error prone (the correct size must be defined everywhere), it's a lot more common common to simply let the array decay, and pass a second argument along with it:

void common_usage(const char *ptr, size_t arr_len);
int main ( void )
{
    char str[] = "some random string";
    common_usage(str, sizeof str/sizeof *str);
    return 0;
}

It looks a lot cleaner, it's a lot more common and just so much easier to maintain.

See examples here

1
votes

The footnote applies to a (function) parameter.

e.g.

void foo(int param_arr[32])
{
     int local_arr[32];
}

param_arr is a parameter to the function - and while it looks like an array, it's really a pointer (an int *). So sizeof param_arr yields the size of an int *.

local_arr is not a parameter. So sizeof yields the size of that array.

0
votes

Having an object of array type that does not produce the size of a pointer is simple: don't do it on a a function argument:

const int foo[32];

printf("hey, foo is %zu bytes\n", sizeof foo);

Will not print sizeof (int *).

This is the normal usage, the text you're quoting is pointing out that when an array is passed to a function, it decays to a pointer, even if the function's prototype specifies an array size.

So this:

static void printsize(int x[100])
{
  printf("the argument is %zu bytes\n", sizeof x);
}

int main(void)
{
  const int foo[100];
  printsize(foo);
  return 0;
}

will print sizeof (int *).

0
votes

Just to clarify your doubt below code might help:

void func(int a[])
{
  sizeof(a) is not equal to sizeof(int) * 10 but equal to sizeof(pointer)
  Because int a[] is adjusted to int * 
}

int main()
{
int a[10];
int *p = a;
//Initialize
//sizeof(a) = sizeof(int) * 10 

//sizeof(p) = sizeof(pointer)

func(a);
}
0
votes

As arrays decay into pointers, when an array is passed to function as a parameter we can illustrate it as declaring parameter as an array as shown below,

void function (char a[])
{ ... }

Now the above declaration is interpreted by compiler differently as a pointer declaration since the function actually receives the pointer to an arrya of type T, as shown below:

void function(char *a)
{ ... }

Therefore, the compiler pretend that the array parameter declared as a pointer (of type char *) and sizeof will give the size of pointer in fact instead of size of array.

Example:

void function (char a[10])
{
    int i = sizeof(a);
    printf("%d\n", i);
}

Output is acutually 4 and not 10.