A reader of my blog sent me a question the other day asking to explain a piece of code with pointers. I found it to be a very interesting puzzle, not just because I had to drop into an object dump with a friend to work through it. The error is consistent, even across platforms. Here is a slightly modified version of the original code. We will call this file bad.c. See if you can notice the error.

[c]
#include

int main()
{
int *a[] = {0,1,2,3,4};

printf(“arr0=%d\n”, *a+0);
printf(“arr1=%d\n”, *a+1);
printf(“arr2=%d\n”, *a+2);
printf(“arr3=%d\n”, *a+3);
printf(“arr4=%d\n”, *a+4);

return 0;
}
[/c]

If you compile that, gcc bad.c you will get a bunch of warnings. Affirming that it is not good to ignore warnings, upon running it you should see output like this.

arr0=0
arr1=4
arr2=8
arr3=12
arr4=16

We ran this on both x64 mac and linux. Same result. What the expected output is is something like this.

arr0=0
arr1=1
arr2=2
arr3=3
arr4=4

Try to figure out the error before going further.

The Error

The error is actually easy to overlook. The int *a[] means an array of int pointers. What was probably intended was an array of ints.

[c]
int main()
{
int *a[] = {0,1,2,3,4};
// should be int a[] = {0,1,2,3,4};
// notice no pointer star before a
printf(“arr0=%d\n”, *a+0);
// also *a+0 should probably be *(a + 0)

}
[/c]

Change that and compile it and done. We get the output. The *a+n expression dereferences the value at the start of the a array, in this case 0, and adds n to it. The *a+0 was probably intended to be *(a + 0) as well, leading to the same output but for a different reason. Mildly interesting.

Ignoring the array of int pointers instead of ints and the dereferencing logic errors, what is interesting is that the error is consistent, even across platforms. Do you know why?

The Puzzle

Confused. Run the error version multiple times you will get the same output. Usually with pointer errors you get changing memory locations and values. But the error output was consistent. I had to work through it in an object dump with my friend Seth Hartbecke to figure out what was going on.

[c]
#include

int main()
{
int *a[] = {0,0,0,0,0};
// change array to all zeros, compile, same output
}
[/c]

Let’s drop into assembly output.

[asm]
000000000040052d

:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 30 sub $0x30,%rsp
400535: 48 c7 45 d0 00 00 00 movq $0x0,-0x30(%rbp) # placing array onto stack
40053c: 00
40053d: 48 c7 45 d8 00 00 00 movq $0x0,-0x28(%rbp)
400544: 00
400545: 48 c7 45 e0 00 00 00 movq $0x0,-0x20(%rbp)
40054c: 00
40054d: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
400554: 00
400555: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
40055c: 00
40055d: 48 8b 45 d0 mov -0x30(%rbp),%rax # move first array value to rax register
400561: 48 89 c6 mov %rax,%rsi # no add just move rax value
400564: bf 94 06 40 00 mov $0x400694,%edi
400569: b8 00 00 00 00 mov $0x0,%eax
40056e: e8 9d fe ff ff callq 400410 400573: 48 8b 45 d0 mov -0x30(%rbp),%rax # move first array value to rax register
400577: 48 83 c0 04 add $0x4,%rax # add 4 to the value in the rax register
40057b: 48 89 c6 mov %rax,%rsi
40057e: bf 9d 06 40 00 mov $0x40069d,%edi
400583: b8 00 00 00 00 mov $0x0,%eax
400588: e8 83 fe ff ff callq 400410 40058d: 48 8b 45 d0 mov -0x30(%rbp),%rax # move first array value to rax register
400591: 48 83 c0 08 add $0x8,%rax # add 8 to the value in the rax register
400595: 48 89 c6 mov %rax,%rsi
400598: bf a6 06 40 00 mov $0x4006a6,%edi
40059d: b8 00 00 00 00 mov $0x0,%eax
4005a2: e8 69 fe ff ff callq 400410 4005a7: 48 8b 45 d0 mov -0x30(%rbp),%rax # move first array value to rax register
4005ab: 48 83 c0 0c add $0xc,%rax # add 12 to the value in the rax register
4005af: 48 89 c6 mov %rax,%rsi
4005b2: bf af 06 40 00 mov $0x4006af,%edi
4005b7: b8 00 00 00 00 mov $0x0,%eax
4005bc: e8 4f fe ff ff callq 400410 4005c1: 48 8b 45 d0 mov -0x30(%rbp),%rax # move first array value to rax register
4005c5: 48 83 c0 10 add $0x10,%rax # add 16 to the value in the rax register
4005c9: 48 89 c6 mov %rax,%rsi
4005cc: bf b8 06 40 00 mov $0x4006b8,%edi
4005d1: b8 00 00 00 00 mov $0x0,%eax

[/asm]

This is interesting. It takes the first value of the array, 0, and adds 4, 8, 12, and 16 to it in sequence to give us our output. Where did those values come from? Have you solved it?

A Bad Case of Pointer Math

This is a bad case of pointer math.

[c]
int main()
{
int *a[] = {0,0,0,0,0}; // array of int pointers, pointers hold addresses

printf(“arr0=%d\n”, *a+0);
printf(“arr1=%d\n”, *a+1);
printf(“arr2=%d\n”, *a+2);
printf(“arr3=%d\n”, *a+3);
printf(“arr4=%d\n”, *a+4);


}
[/c]

The first piece of the puzzle is that int *a[] is an array of pointers. The initialization {0,0,0,0,0} or {0,1,2,3,4} are, rightly so according to the compiler, seen as pointers holding addresses. There are instances where absolute addresses are used, though it is more common in embedded programming.

The second piece to the puzzle is the 0, 4, 8, 12, 16 in the assembly. These are equivalent to 0 * 4, 1 * 4, 2 * 4, 3 * 4, and 4 * 4 respectively.

The int *a[] being an array of pointers, the compiler is doing pointer math. The *a + 1 value is being evaluated as an int pointer + n. In this case ints are 4 bytes and according to pointer math, a + n items get translated to 4 bytes * n items.

[c]
int main()
{
int *a[] = {0,0,0,0,0}; // array of int pointers, pointers hold addresses

printf(“arr0=%d\n”, *a+0); // *a+0 == 4 bytes * 0 == 0
printf(“arr1=%d\n”, *a+1); // *a+1 == 4 bytes * 1 == 4
printf(“arr2=%d\n”, *a+2); // *a+2 == 4 bytes * 2 == 8
printf(“arr3=%d\n”, *a+3); // *a+3 == 4 bytes * 3 == 12
printf(“arr4=%d\n”, *a+4); // *a+4 == 4 bytes * 4 == 16

}
[/c]

In the end a consistent, and somewhat interesting, case of bad pointer math. All from one little character.