gabber12
gabber12

Reputation: 1124

Inconsistency in using pointer to an array and address of an array directly

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?

Upvotes: 8

Views: 489

Answers (4)

Grijesh Chauhan
Grijesh Chauhan

Reputation: 58291

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).

Upvotes: 9

AnT stands with Russia
AnT stands with Russia

Reputation: 320709

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.

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726987

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

Upvotes: 4

ouah
ouah

Reputation: 145899

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.

Upvotes: 1

Related Questions