1

I'm studying C programming language and its bit operators. I've written codes like below and I expected that the result of the codes are same. But the reality is not.

#include <stdio.h>
#define N 0

int main() {
    int n = 0;
    printf("%d\n", ~0x00 + (0x01 << (0x20 + (~n + 1))));
    printf("%d\n", ~0x00 + (0x01 << (0x20 + (~N + 1))));
    return 0;
}

I assumed that the machine represent numbers as 2's complement on 32-bit. They both have to be -1 which is all bits are 1 but first one is 0 and second one is -1. I think both are exactly same code except whether using variable or constant.

I used gcc with option -m32 on Mac of i5 CPU.

What's wrong with it?

Thanks.

lbyeoksan
  • 123
  • 7
  • 1
    On my Win XP I get this warning on the second printf line: **warning: left shift count >= width of type** – Marichyasana Jul 20 '13 at 04:15
  • Did the code give you any warnings? It does for me because you're shifting a 32b value more than 32 bits. – Jens Jul 20 '13 at 04:16
  • Compiler said "warning: left shift count >= width of type" for second code.Is it a problem to shift 32bits on 32-bit value? – lbyeoksan Jul 20 '13 at 04:18
  • 1
    well it is "undefined behavior" as stated `6.5.7.3: "The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined."` – ludesign Jul 20 '13 at 04:21
  • possible duplicate of [Type of integer literals not int by default?](http://stackoverflow.com/questions/8108642/type-of-integer-literals-not-int-by-default) – Barmar Jul 20 '13 at 04:22
  • 1
    thanks everybody. So it's undefined if I'm shifting bits more than or "equal to" the width of the type. – lbyeoksan Jul 20 '13 at 04:28
  • 1
    @lbyeoksan - Yes, as far as I know it might work on some cpus but on x86 it results in undefined behavior. Keep up with learning C :) – ludesign Jul 20 '13 at 04:32
  • @lbyeoksan: It's not undefined behavior, it's very well defined. But since you compute your result in two different ways and those two have different shift rules, you end up with different results. But it's not undefined. – Jens Jul 20 '13 at 05:01
  • If you've received a satisfying answer, would you mind tagging it as such? – Jens Jul 21 '13 at 07:19

2 Answers2

5

The short answer

You're evaluating the same expression in two different ways—once at runtime on an x86, and once at compile time. (And I assume you've disabled optimizations when you compile, see below.)

The long answer

Looking at the disassembled executable I notice the following: the argument to the first printf() is computed at runtime:

movl   $0x0,-0x10(%ebp)
mov    -0x10(%ebp),%ecx  ; ecx = 0 (int n)
mov    $0x20,%edx        ; edx = 32
sub    %ecx,%edx         ; edx = 32-0 = 32
mov    %edx,%ecx         ; ecx = 32
mov    $0x1,%edx         ; edx = 1
shl    %cl,%edx          ; edx = 1 << (32 & 31) = 1 << 0 = 1
add    $0xffffffff,%edx  ; edx = -1 + 1 = 0

The shift is performed by an x86 SHL instruction with %cl as its operator. As per Intel manual: "The destination operand can be a register or a memory location. The count operand can be an immediate value or register CL. The count is masked to five bits, which limits the count range to 0 to 31. A special opcode encoding is provided for a count of 1."

For the above code, that means that you're shifting by 0, thus leaving the 1 in place after the shift instruction.

In contrast, the argument to the second printf() is essentially a constant expression that is computed by the compiler, and the compiler does not mask the shift amount. It therefore performs a "correct" shift of a 32b value: 1<<32 = 0 It then adds -1 to that—and you see the 0+(-1) = -1 as a result.

This also explains why you see only one warning: left shift count >= width of type and not two, as the warning stems from the compiler evaluating the shift of a 32b value by 32 bits. The compiler did not issue any warning regarding the runtime shift.

Reduced test case

The following is a reduction of your example to its essentials:

  #define N 0
  int n = 0;

  printf("%d %d\n", 1<<(32-N) /* compiler */, 1<<(32-n) /* runtime */);

which prints 0 1 demonstrating the different results of the shift.

A word of caution

Note that the above example works only in -O0 compiled code, where you don't have the compiler optimize (evaluate and fold) constant expressions at compile time. If you take the reduced test case and compile it with -O3 then you get the same and correct results 0 0 from this optimized code:

movl   $0x0,0x8(%esp)
movl   $0x0,0x4(%esp)

I would think that if you change the compiler options for your test, you will see the same changed behavior.

Note There seems to be a code-gen bug in gcc-4.2.1 (and others?) where the runtime result is just off 0 8027 due to a broken optimization.

Jens
  • 8,423
  • 9
  • 58
  • 78
  • I think that's the point of his question: why does using a macro give a different result than using an int variable. I think it's because of the default promotion rules, described in the question I linked to. – Barmar Jul 20 '13 at 04:32
  • Yes, I just confirmed that fact. That's why only second one is right. difference between runtime and compile time. Thanks. – lbyeoksan Jul 20 '13 at 04:32
  • @Barmar: I've extended the explanation a bit. The reason why the compiler gets it right is because the compiler doesn't mask the shift value, whereas the CPU does. – Jens Jul 20 '13 at 04:54
  • @Jens Thanks. I have to go to work. I'll read your answers for my question as soon as I'll be back. thanks again. – lbyeoksan Jul 20 '13 at 05:06
  • @Jens Thanks! I understand the behaviors. Optimization! – lbyeoksan Jul 20 '13 at 13:41
5

A simplified example

unsigned n32 = 32;
printf("%d\n", (int) sizeof(int));  // 4
printf("%d\n",  (0x01 << n32));     // 1
printf("%d\n",  (0x01 << 32));      // 0

You get UB in (0x01 << n32) as the shift >= width of int. (Looks like only 5 lsbits of n32 participated in the shift. Hence a shift of 0.)

You get a UB in (0x01 << 32) as the shift >= width of int. (Looks like complier performed the math with more bits.) This UB could have been the same as above.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    Modern processors use only a few of the LSBits to determine the shift count for the shift instructions. e.g. Pentium "The count is masked to five bits". BITD the 8088 (an 8-bit machine) used all 8 bits for the shift, thus potentially shifting a register 255 times and taking 255 clock cycles. This made for a theoretical bad interrupt response time as it could took a _long_ time for a shift to finish before the interrupt could be acknowledged. The following processor, 80186 only used lower bits. This is the hack used to distinguish these 2 very similar processors. – chux - Reinstate Monica Jul 20 '13 at 04:51
  • Correction, the 8088 is a 16 bit processors. But the shift parameter was still 8 bits. – chux - Reinstate Monica Jul 20 '13 at 05:15