Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sizeof applied to array types

Tags:

arrays

c

sizeof

c11

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.

like image 829
Jack Avatar asked Feb 12 '23 01:02

Jack


2 Answers

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

like image 99
Elias Van Ootegem Avatar answered Feb 13 '23 22:02

Elias Van Ootegem


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
like image 39
juanchopanza Avatar answered Feb 13 '23 20:02

juanchopanza