22

For the program :

#include<stdio.h>
int main(void)
{

    int (*a)[2];
    int b[5];

    printf("sizeof(int) : %zu\n", sizeof(int)); 
    printf("sizeof(int*) : %zu\n", sizeof(int*));

    printf("sizeof(b) : %zu\n",sizeof(b));
    printf("sizeof((int*)b) : %zu\n",sizeof((int*)b));
    printf("sizeof(&b[0]) : %zu\n",sizeof(&b[0]));

    printf("sizeof(a) : %zu\n",sizeof(a));
    printf("sizeof(a[0]) : %zu\n",sizeof(a[0]));
    printf("sizeof(a[1]) : %zu\n",sizeof(a[1]));

    return 0;
}

Output is :

sizeof(int) : 4 -> Fact 1
sizeof(int*) : 8 -> Fact 2
sizeof(b) : 20 -> Case 1
sizeof((int*)b) : 8 -> Case 2
sizeof(&b[0]) : 8 -> Case 3
sizeof(a) : 8 -> Case 4
sizeof(a[0]) : 8 -> Case 5
sizeof(a[1]) : 8 -> Case 6

Questions/Observations (in Case Order ) :

  1. Is Case 1 output 20 because b was declared as an array of integers ie int[]? The total block in bytes is returned as confirmed by Fact1. Isn't it?

  2. I guess casting b to int* made the difference here. Here b is considered a pointer. I confirmed this using Fact2. Right or wrong?

  3. &b[0] decays to a pointer b. The output coincides with Fact2 .

  4. I expected 16 here but I got 8 as the output. I concluded that this is because a is afterall a pointer and the output coincides with Fact2 . I got the output similar to Question 2.

  5. a[0] is pointer. The output coincides with Fact2

  6. a[1] is pointer. The output coincides with Fact2

Please answer the questions and correct me if any of the observations are wrong.

dbush
  • 205,898
  • 23
  • 218
  • 273
sjsam
  • 21,411
  • 5
  • 55
  • 102
  • 7
    The expression `&b[0]` isn't decaying to a pointer, it *is* a pointer. – Some programmer dude Apr 28 '16 at 12:14
  • @JoachimPileborg : Thanks for pointing that. It was more a problem with my terminology.. I see the point now.. But I may not edit the question as I feel this will be useful for somebody else too.. – sjsam Apr 28 '16 at 12:20
  • `size_t` is unsigned! Wrong type-specifier in format string. And there are a lot of wrong assumptions. – too honest for this site Apr 28 '16 at 12:23
  • @Olaf : Thanks, made the change and confirmed that the result did't change – sjsam Apr 28 '16 at 12:26
  • @MohitJain: Please read the rest of the paragraph: "... to a size_t **or the corresponding signed integer** type argument, ..." As `size_t` is unsigned, you have to use `u`. Not sure why they allow `d` actually, but maybe for symmetry or simplification. – too honest for this site Apr 28 '16 at 12:28
  • @Olaf I also wondered about [zd](http://stackoverflow.com/questions/32916575/how-to-use-zd-specifier-with-printf) myself. – chux - Reinstate Monica Apr 28 '16 at 12:31
  • @JoachimPileborg : What if &b[0] is passed as an argument to a function. Would the phrase `decays to a pointer` be used then? – sjsam Apr 28 '16 at 12:57
  • 1
    sjsam, Detail: "decay" is used by many to describe what the C spec details as "...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..." §6.3.2.1 3 `&b[0]` is a pointer, not an array. No need to "decay". – chux - Reinstate Monica Apr 28 '16 at 13:16
  • 1
    @sjsam No, because `&b[0]` is already a pointer, the address-of operator gives you a pointer to `b[0]`. If you passed `b` as an argument *then* it would decay to a pointer. – Some programmer dude Apr 28 '16 at 14:02
  • @sjsam Moreover `sizeof` is **not** a function. That's why array argument gives correct size. Array depletes to pointer when it is used in an expression (passed to a function argument etc) – Mohit Jain Apr 28 '16 at 14:27
  • I want to add one point: if you run your code on 32 and 64 bit system, then it's obvious that case5/6 cannot be derived from fact 2 x86 sizeof(int) : 4 sizeof(int*) : 4 sizeof(b) : 20 sizeof((int*)b) : 4 sizeof(&b[0]) : 4 sizeof(a) : 4 sizeof(a[0]) : 8 sizeof(a[1]) : 8 x64 sizeof(int) : 4 sizeof(int*) : 8 sizeof(b) : 20 sizeof((int*)b) : 8 sizeof(&b[0]) : 8 sizeof(a) : 8 sizeof(a[0]) : 8 sizeof(a[1]) : 8 – user1602017 May 06 '16 at 19:36

3 Answers3

16

Please answer the questions and correct me if any of the observations are wrong.

  1. Is Case 1 output 20 because b was declared as an array of integers ie int[]? The total block in bytes is returned as confirmed by Fact1. Isn't it?

Yes, the result shows the sizeof(int [5]). So from Fact1, the size is 5*4

  1. I guess casting b to int* made the difference here. Here b is considered a pointer. I confirmed this using Fact2. Right or wrong?

Right. But adding more info: sizeof needs only the type of expression and it does not evaluate expression (for value) unless it is VLA type. (From section 6.5.3.4 The sizeof operator of C99 specs)

Because you are applying cast on final result, anything after that does not matter.

  1. &b[0] decays to a pointer b. The output coincides with Fact2 .

No and yes. Type of b[0] is int and thus type of &b[0] is already int *(Recall that [...] binds tighter than &). There is no decaying. And yes the output coincides with Fact2.

  1. I expected 16 here but I got 8 as the output. I concluded that this is because a is afterall a pointer and the output coincides with Fact2 . I got the output similar to Question 2.

a as pointer to array 2 of int. So the printed size is of pointer (to an int array).

int (*a)[2]; declares a as pointer to array 2 of int. So you get the size of pointer to array.

To get the desired result (size of array 2 of pointers to int) use: int *a[2];

int (*a)[2];

a           anonymous
+----+      +----+----+
| a  |----->|int |int |
+----+      +----+----+

int *b[2];

b  
+----+----+
|int*|int*|
+----+----+
b[0] b[1]
  1. a[0] is pointer. The output coincides with Fact2
  2. a[2] is pointer. The output coincides with Fact2

As stated earlier, a is a pointer to array 2 of int. So a[index] is an array 2 if int. So, type of a[0] and a[1] are array 2 of int. So the output is 2*4 from Fact 1.
Possibly irrelevant to this answer but a is uninitialized and using it in expression would cause undefined behaviour. Though it is fine to use in sizeof


To understand the output, let's analyse the type of argument of sizeof

printf("sizeof(b) : %zu\n",sizeof(b));             // int [5]
printf("sizeof((int*)b) : %zu\n",sizeof((int*)b)); // int *
printf("sizeof(&b[0]) : %zu\n",sizeof(&b[0]));     // int *

printf("sizeof(a) : %zu\n",sizeof(a));             // int (*) [2]
printf("sizeof(a[0]) : %zu\n",sizeof(a[0]));       // int [2]
printf("sizeof(a[1]) : %zu\n",sizeof(a[1]));       // int [2]

A portable program (not foolproof) to confirm the types looks like:

assert(sizeof(b) == sizeof(int [5]));
assert(sizeof((int*)b) == sizeof(int *));
assert(sizeof(&b[0]) == sizeof(int *));

assert(sizeof(a) == sizeof(int(*)[2]));
assert(sizeof(a[0]) == sizeof(int[2]));
assert(sizeof(a[1]) == sizeof(int[2]));
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
13

The sizeof operator is one of the few things that can distinguish between an array (assuming it's not a function parameter) and a pointer.

  1. b is recognized as an array of 5 elements where each is 4 bytes, so sizeof(b) evaluates to 20.
  2. The cast converts the array to a pointer in a similar way as passing it to a function would. So the size is 8.
  3. This is not actually decaying to a pointer. It is a pointer. You're taking the address of an int, so of course the type is int *. Addressing one of your comments, it is still not accurate to say the expression &b[0] decays to a pointer if you pass it to a function, because it is in fact a pointer, not an array.
  4. Since a is a pointer to an array, the size is the size of a pointer, i.e. 8. This is different from int *c[2], which is an array of pointers and would have size 16.
  5. a[0] is not a pointer but an array of size 2. The syntax a[0] is equivalent to *(a + 0). So since a is a pointer to an array, dereferencing a gives us an array. Since each element is 4 bytes the size is 8. If a was defined as int (*a)[3] then sizeof(a[0]) evaluates to 12.
  6. Similar to number 5, a[1] is an array of size 2. So sizeof(a[1]) evaluates to 8 because it is an array of 2 elements of size 4.

An example of how to use a is as follows:

int (*a)[2];
int d[3][2];

a=d;
d[0][0]=1;
d[0][1]=2;
d[1][0]=3;
d[1][1]=4;
d[2][0]=5;
d[3][1]=6;

printf("a00=%d\n",a[0][0]);
printf("a01=%d\n",a[0][1]);
printf("a10=%d\n",a[1][0]);
printf("a11=%d\n",a[1][1]);
printf("a20=%d\n",a[2][0]);
printf("a21=%d\n",a[3][1]);

Output:

a00=1
a01=2
a10=3
a11=4
a20=5
a21=6

You also use this when passing a 2D array to a function:

void f(int (*a)[2]) 
{
    ...
}

int main()
{
    int x[3][2];
    f(x);
}
dbush
  • 205,898
  • 23
  • 218
  • 273
  • 1
    Great example , Thank you – sjsam Apr 29 '16 at 01:54
  • 1
    I was about to write an answer and realized you set the bounty. What do you want? Are you looking for more answers, more awareness of these answers or trying to discuss what's already here? – Harry May 04 '16 at 20:06
  • 1
    @Harry More about awareness. There are some subtle points in play that may not be immediately apparent. Particularly, I initially missed what was happening in points 5 and 6 because the values happened to be the same but caught them on closer inspection. If you think you have more to add than what's here, feel free to add your own answer. – dbush May 04 '16 at 20:12
  • 1
    @dbush : Appreciate granting a bounty for this one :D. In fact I returned to C programming after long six years and the underlying issue here was just a misunderstanding about `pointer to array` as evident from points 5 and 6. Again it is great you setting an example for this forum. Hats off ! – sjsam May 09 '16 at 10:39
  • @SumitTrehan : In fact this one is as good as the accepted one, but the other one had a lil diagram which made me easily visualize the concept. – sjsam May 09 '16 at 10:48
  • If you bear in mind that *array* and *pointer* are actually different things, there is not some sort of quantum mechanical particle that changes when you look at it, then there no mystery or confusion – M.M May 10 '16 at 05:36
2

Here is a bit of individual research on the subject. I've run your test code in four different environments, two 64bit and two 32bit.
I used three different compilers: llvm, gcc and mipsPro cc.
Here is the commented comparison of the results:

// 64-bit environment - all compilers
sizeof(int) :     4 -> Fact 1       -32 bit int -> 4 bytes   
sizeof(int*) :    8 -> Fact 2       -this and other pointers in a 64-bit system are 8-bytes long
sizeof(b) :      20 -> Case 1       -array of 5 32 bit ints -> 20 bytes
sizeof((int*)b) : 8 -> Case 2
sizeof(&b[0]) :   8 -> Case 3
sizeof(a) :       8 -> Case 4
sizeof(a[0]) :    8 -> Case 5       -array of two 4 byte ints
sizeof(a[1]) :    8 -> Case 6       -array of two 4 byte ints

// 32-bit environments - all compilers
sizeof(int) :     4 -> Fact 1       -32 bit int -> 4 bytes 
sizeof(int*) :    4 -> Fact 2       -this and other pointers in a 32-bit system are 4-bytes long
sizeof(b) :      20 -> Case 1       -array of 5 32 bit ints -> 20 bytes
sizeof((int*)b) : 4 -> Case 2
sizeof(&b[0]) :   4 -> Case 3
sizeof(a) :       4- > Case 4
sizeof(a[0]) :    8 -> Case 5       -array of two 4 byte ints
sizeof(a[1]) :    8 -> Case 6       -array of two 4 byte ints

Interpretation - all results consistently match the following pattern:

  • Size of int used to depend on the compiler, and perhaps still does, AFAIK. It is in all tested environments and compilers 4-byte (Fact 1).
  • Size of all pointers defaults to the environment, either 64bit or 32bit (Fact 2, Case 2, 3, 4).
  • Size of an array of two four-byte ints equals 2*sizeof(int) (Case 5, 6).
  • a[0] can be rewritten as *a; a[1] can also be written as *(a + 1). The following SO post elaborates it in detail.

    Hope this can contribute to your topic.
Community
  • 1
  • 1
user3078414
  • 1,942
  • 2
  • 16
  • 24