Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsigned minus unsigned difference between 32 and 64-bit

The situation:
I have a piece of code that works when compiled for 32-bits but fails when compiled for 64-bits with gcc 4.6. After identifying the problem and reading up on the standards, I cannot really understand why it is working for 32-bits. I hope someone can explain what is going on.

The code (somewhat simplified and cut down to the interesting parts):

// tbl: unsigned short *, can be indexed with positive and negative values
// v: unsigned int
// p: unsigned char *
tmp = tbl[(v >> 8) - p[0]]; // Gives segfault when not compiled with -m32

When compiled with -m32 the code works. When compiled without -m32 it gives a segfault. The reason for the segfault is that (v >> 8) - p[0] is interpreted as an unsigned int when compiled for 64-bit which for "negative" results will be way off.

According to this question, the C99 standard says the following:
6.2.5c9: A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

From this it seems that unsigned minus unsigned will always result in unsigned output which is consistent with what happens in the 64-bit case. This does not seem to happen in the 32-bit case which is what I find extremely strange.

Can anyone explain what is happening in the 32-bit case?

like image 459
Leo Avatar asked Sep 05 '12 13:09

Leo


1 Answers

In both cases you get a very large number because the unsigned int wraps around, but in the 32-bit case, the pointer arithmetic also wraps around, and so it cancels out.

In order to do the pointer arithmetic, the compiler promotes the array index to the same width as the pointer. So for unsigned with 32-bit pointers, you get the same result as int with 32-bit pointers.

For example,

char *p = (char *) 0x1000;

// always points to 0x0c00
// in 32-bit, the index is 0xfffffc00
// in 64-bit, the index is 0xfffffffffffffc00
int r = p[(int) -0x400]; 

// depends on architecture
// in 32-bit, the index is 0xfffffc00 (same as int)
// in 64-bit, the index is 0x00000000fffffc00 (different from int)
int r = p[(unsigned) -0x400];
like image 84
Dietrich Epp Avatar answered Oct 10 '22 13:10

Dietrich Epp