Difference in results when using int and size_t

684 views Asked by At

I was reading an article on usage of size_t and ptrdiff_t data types here, when I came across this example:

enter image description here

The code:

int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); //Error
printf("%i\n", *ptr);

I am unable to understand a couple of things. First, how can adding a signed and an unsigned number cast the enter result into unsigned type? If the result is indeed 0xFFFFFFFF of unsigned type, why in a 32 bit system, while adding it with ptr, will it be interpreted as ptr-1, given that the number is actually unsigned type and the leading 1 should not signify sign?

Second, why is the result different in 64 bit system?

Can anyone explain this please?

3

There are 3 answers

0
2501 On BEST ANSWER

1. I am unable to understand a couple of things. First, how can adding a signed and an unsigned number cast the enter result into unsigned type?

This is defined by integer promotions and integer conversion rank.

6.3.1.8 p1: Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

In this case unsigned has a higher rank than int, therefore int is promoted to unsigned.

The conversion of int ( -2 ) to unsigned is performed as described:

6.3.1.3 p2: Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type

2. If the result is indeed 0xFFFFFFFF of unsigned type, why in a 32 bit system, while adding it with ptr, will it be interpreted as ptr-1, given that the number is actually unsigned type and the leading 1 should not signify sign?

This is undefined behavior and should not be relied on, since C doesn't define pointer arithmetic overflow.

6.5.6 p8: If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

3. Second, why is the result different in 64 bit system?

( This assumes( as does the picture ) that int and unsigned are 4 bytes. )

The result of A and B is the same as described in 1., then that result is added to the pointer. Since the pointer is 8 bytes and assuming the addition doesn't overflow( it still could if ptr had a large address, giving the same undefined behavior as in 2. ) the result is an address.

This is undefined behavior because the pointer points way outside of the bounds of the array.

2
didierc On

On most 64bits, int are 32bits, but on 32bits systems, pointers are also 32bits.

Recall that in 32bits arithmetic - on two's complement based hardware, adding 0xFFFFFFFF is almost the same as subtracting 1: it overflows and you get that same number minus 1 (it's the same phenomenon when you add 9 to a number between 0 and 9, you get than number minus 1 and a carry). On that type of hardware, the encoding of -1 is actually that same value 0xFFFFFFFF, only the operation is different (signed add versus unsigned add), and so a carry will be produced in the unsigned case.

On 64bits pointers are... 64bits. Adding a 32bits value to a 64bits one requires to extend that 32bit value to 64. unsigned values are zero extended (ie. the missing bits are just filled with zeros), while signed values are sign extended (ie. missing bits are filled with the sign bit value).

In this case, adding an unsigned value (which will therefore not be sign extended) will not overflow, thus yielding a very different value from the original.

0
mafso On

The operands of the expression A + B are subject to usual arithmetic conversion, covered in C11 (n1570) 6.3.1.8 p1:

[...]

Otherwise, the integer promotions [which leave int and unsigned int unchanged] are performed on both operands. Then the following rules are applied to the promoted operands:

  • If both operands have the same type, [...]
  • Otherwise, if both operands have signed integer types or both have unsigned integer types, [...]
  • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
  • [...]

The types int and unsigned int have the same rank (ibid. 6.3.1.1 p1, 4th bullet); the result of the addition has type unsigned int.

On 32-bit systems, int and pointers usually have the same size (32 bit). From a hardware-centric point of view (and assuming 2's complement), subtracting 1 and adding -1u is the same (addition for signed and unsigned types is the same!), so the access to the array element appears to work.

However, this is undefined behaviour, as array doesn't contain a 0x100000003rd element.

On 64-bit, int usually has still 32 bit, but pointers have 64 bit. Thus, there is no wraparound and no equivalence to subtracting 1 (from a hardware-centric point of view, the behaviour is undefined in both cases).

To illustrate, say ptr is 0xabcd0123, adding 0xffffffff yields

  abcd0123
+ ffffffff

 1abcd0122
 ^-- The 1 is truncated for a 32-bit calculation, but not for 64-bit.