8

This code sample prints the array correctly.

int b[2] = {1, 2};
int *c = &b;
int  i, j,k = 0;
for (i = 0;i < 2; i++) {
    printf("%d ", *(c+i));
}

while this one prints two garbage values.

int b[2] = {1, 2};
int  i, j,k = 0;
for (i = 0;i < 2; i++) {
    printf("%d ", *(&b+i));
}

why are the two code samples behaving differently?

gabber12
  • 1,114
  • 2
  • 11
  • 18

4 Answers4

9

The declaration:

int b[2] = {1, 2};

Creates an array of two int with values 1, 2.
Suppose in a system size of int is 4-bytes, then the array b[] should be stored in memory something like as follows:

first ele        +----------+                
     (b + 0) ---►|     1    | 0xbf5c787c  <----- &b ,    (c + 0)
next ele         +----------+ 
     (b + 1) ---►|     2    | 0xbf5c7880  <------------- (c + 1)
                 +----------+              
     (b + 2) ---►|     ?    | 0xbf5c7884  <----- (&b + 1) next array  
                 +----------+                    
             ---►|     ?    | 0xbf5c7888  
                 +----------+ 
             ---►|     ?    | 0xbf5c788c  <----- (&b + 2) next array  
                 +----------+      
             ---►|     ?    | 0xbf5c7890
                 +----------+               

? means garbage value
b[] array in memory from 0xbf5c787c to 0xbf5c7880  
each cell is four bytes 

In above diagram memory cells with value ? means garbage values and not allocated (memory from 0xbf5c7884 is not allocated in for our array). The values 1, 2 are stored in memory at address 0xbf5c787c and 0xbf5c7880, that is allocated in array b[].

Let's instead of printing values, we print addresses of memory that you access in your code using (c + i) and (&b + i), for this consider following program:

#include<stdio.h>
int main(){
  int b[2] = {1, 2}; 
  int  i = 0;
  int *c = &b; //Give warning: "assignment from incompatible pointer type" 
  printf("\n C address: ");  // outputs correct values 
  for (i = 0; i < 2; i++) {
    printf("%p ", (void*)(c + i));
  }
  printf("\n B address: ");  // outputs incorrect values/ and behaving differently 
  for (i = 0; i < 2; i++) {
    printf("%p ", (void*)(&b + i));  // Undefined behavior 
  }
  return 1;
}

Outputs:

 C address: 0xbf5c787c 0xbf5c7880 
 B address: 0xbf5c787c 0xbf5c7884 

Check this code working @Codepade
Notice, (c + i) prints correct address of cells with value 1, 2 hence outputs in your first code is correct. Whereas (&b + i) prints address value that is not allocated to array b[] (that is outside of array b[]) And accessing this memory gives undefined behaviour(unpredictable) at runtime.

Actually there is difference between b and &b.

  • b is an array and its type is int[2], b decays into int* as address for first element in most expressions (read: some exceptions where array name not decaying into a pointer to first element?). And b + 1 points to next int elements in array (notice the diagram).

  • &b is address of complete array and its type is int(*)[2], (&b + 1) points to next array of type int[2] that is not allocated in your program (notice in diagram that where (&b + 1) points).

To know some other interesting differences between b and &b read: What does sizeof(&array) return?

In first code snipe, when you do c = &b, you are assigning array's address to int* (in our example 0xbf5c787c). With GCC compiler this statement will give warning: "assignment from incompatible pointer type".
Because c is pointer to int, so *(c + i) prints integer stored at address (c + i). For i = 1 the value (c + 1) points to second element in array (in our example at 0xbf5c7880) hence *(c + 1) prints 2 correctly.

Regarding assignment int *c = &b; in first code I highly suggest read @AndreyT's answer below. The correct and simple way to access array elements using pointer will be as follows:

int b[2] = {1, 2};
int *c = b;   // removed &, `c` is pointer to int  
int i;
for (i = 0; i < 2; i++){
    printf("%d ", *(c + i)); 
 // printf("%d ", c[i]); // is also correct statement 
}

In your second code, adding i to &b make it pointing to outside allocated memory and in printf statement you access memory using * dereference operator cause invalid memory access and behavior of this code at run time is Undefined. That is the reason that second piece of code behaving differently at different execution.

Your code compiles because syntactically it correct, But at runtime accessing of unallocated memory can be detected by OS kernel. This may causes OS kernel send a signal core dump to the process which caused the exception (interesting to note: as OS detects memory right violation by a process -- An invalid access to valid memory gives: SIGSEGV, and access to an invalid address gives: SIGBUS). In worth case your program may execute without any failure and produce garbage results.

Regarding second code, correct way to print array using 'pointer to array' will be as below:

#include<stdio.h>
int main(){
  int b[2] = {1, 2}; 
  int  i;
  int (*c)[2] = &b;   // `c` is pointer to int array of size 2
  for(i = 0; i < 2; i++){
     printf(" b[%d] = (*c)[%d] = %d\n", i, i, (*c)[i]); // notice (*c)[i]
  }
  return 1;
}

Output:

b[0] = (*c)[0] = 1
b[1] = (*c)[1] = 2  

Check @codepade. Point to be notice parenthesis around *c is needed as precedence of [] operator is higher then * dereference operator (whereas if you use pointer to int you don't need parentheses as in above code).

Community
  • 1
  • 1
Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
  • "This statement should give you Warning for type mismatch assignment." — Not necessarily; a pointer to array converts implicitly to a pointer to its first element. IMHO this is a crucial detail. – Potatoswatter Jul 16 '13 at 13:23
  • @Potatoswatter But I just checked with GCC I got:`assignment from incompatible pointer type`. Should I remove this?? – Grijesh Chauhan Jul 16 '13 at 13:33
  • @Potatoswatter: That's not true. *Array itself* converts implicitly to a pointer to its first element. *Pointer to an array* does not convert implicitly to a pointer to the first element. The initialization in first code sample is erroneous, i.e. it is a constraint violation. This is why compiler complains about it. – AnT stands with Russia Jul 16 '13 at 17:47
  • 1
    @GrijeshChauhan Nope, looks like I was wrong. Actually even wronger than came out in that comment. Good thing I just reviewed; Andrey is right. – Potatoswatter Jul 17 '13 at 03:55
5

This is because of the pointer type to which the pointer arithmetic operation ptr+i is applied:

  • In the first case, you add i to a pointer to int, which is the same as indexing an array. Since the pointer to an array is the same as the pointer to its first element, the code works.
  • In the second case, you add i to a pointer to an array of two ints. Therefore, the addition puts you beyond the allocated memory, causing undefined behavior.

Here is a quick illustration of this point:

int b[2] = {1,2};
printf("%p\n%p\n%p\n", (void*)&b, (void*)(&b+1), (void*)(&b+2));

On a system with 32-bit ints this prints addresses separated by eight bytes - the size of int[2]:

0xbfbd2e58
0xbfbd2e60
0xbfbd2e68
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1
int *c = &b;     

This is actually not valid. You need a cast.

int *c = (int *) &b; 

The two expressions:

 *(c+i)

 and 

 *(&b+i)

are not same. In the first expression i is added to a int * and in the second expression i is added to a int (*)[2]. With int *c = (int *) &b; you convert a int (*)[2] to an int *. c + i points to the i-th int element of c but &b+i points to the int [2] element of b moving the pointer value outside the actual array object.

ouah
  • 142,963
  • 15
  • 272
  • 331
  • 2
    It's true that `int *c = &b;` is not valid, but adding a cast is not the solution. If you want the address of the first element, write `int *c = b;` or `int *c = &b[0];`. If you want the address of the array, write `int (*c)[2] = &b;`. Converting a pointer from one type to another (incompatible) type is rarely the answer -- unless you're asking a rather odd question. – Keith Thompson Jul 15 '13 at 22:53
  • @KeithThompson I read @Andrey's answer, having doubt that "Is it possible that with some architecture/compiler `int *c = &b;` give undefined behaviour **?** Can I ignore warning generated by compiler as I know value-wise both `&b` and `b` are same (semantically both are different). – Grijesh Chauhan Oct 22 '13 at 06:38
  • 1
    @GrijeshChauhan: Given that `b` is of type `arr[2]`, `int *c = &b;` is a constraint violation. A conforming compiler *must* diagnose it and *may* reject it. If it rejects it, its behavior is not merely undefined; it has no behavior. Just don't do that. – Keith Thompson Oct 22 '13 at 06:41
1

The first code is broken. The assignment

int *c = &b;

is invalid. The right-hand side has type int (*)[2], while the object on the left has type int *. These are different, incompatible types. The compiler should have told you about this error by issuing a diagnostic message. Don't ignore diagnostic messages issued by compilers.

The code, despite suffering from the above problem, was accepted by the compiler due to a non-standard compiler extension, which allowed the compiler to convert int (*)[2] pointer to int * type preserving the numerical value of the pointer (the physical address). So, you ended up with int * pointer pointing to the beginning of your int [2] array. Not surprisingly, accessing memory through that pointer lets you to see the contents of the array.

Your second code is also broken in more than one way. It doesn't suffer from the first problem, since you don't force any conversion of &b value (which, again, has type int (*)[2]) to anything else, but apply pointer arithmetic directly to &b. According to the rules of pointer arithmetic, the expression &b + 1 produces a pointer that points beyond the original b array. Dereferencing such pointer is illegal. So, *(&b + 1) already produces undefined behavior. On top of that, the expression *(&b + 1) has type int [2], which decays to pointer type int *. So, in your second code you are attempting to print an int * value with %d format specifier. This is also undefined behavior. The manifestations of that undefined behavior is what you see in your second example.

In other words, in the first piece of code you got luckier than in the second one, which is why the output of the former looks more meaningful.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Andrey Is it possible that with some architecture/compiler `int *c = &b;` give undefined behaviour **?** As I know value wise both `&b` and `b` are same but semantically both are different, so I feel a code because of `int *c = &b;` compiles with warning but I can ignore this warning. Is my assumption is correct? – Grijesh Chauhan Oct 22 '13 at 06:34