// In this example the compiler reasons about code and eliminates // checking the condition that it judges to be always true. The // result may be a bit surprising. // Consider the following variant of our Hello loop: [sergey@thepond cs59]$ cat hello-loop-uint.c #include int main() { unsigned int i = 10; // <-- unsigned is added here while( i >= 0 ){ i = i - 1; puts( "Hello" ); } return 42; } // This compiles without any warnings... [sergey@thepond cs59]$ gcc -Wall -o hello-loop-uint hello-loop-uint.c // ...but when run it prints "Hello" endlessly. // Let's see why: [sergey@thepond cs59]$ gdb ./hello-loop-uint GNU gdb (GDB) 17.2 Copyright (C) 2025 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later [..skipped..] (gdb) disas main Dump of assembler code for function main: 0x0000000000001139 <+0>: push %rbp 0x000000000000113a <+1>: mov %rsp,%rbp 0x000000000000113d <+4>: sub $0x10,%rsp 0x0000000000001141 <+8>: movl $0xa,-0x4(%rbp) // "i" is saved 0x0000000000001148 <+15>: subl $0x1,-0x4(%rbp) // and decremented 0x000000000000114c <+19>: lea 0xeb1(%rip),%rax # 0x2004 0x0000000000001153 <+26>: mov %rax,%rdi 0x0000000000001156 <+29>: call 0x1030 0x000000000000115b <+34>: nop 0x000000000000115c <+35>: jmp 0x1148 // <<-- but never checked! Observe // the unconditional JMP back. End of assembler dump. // The compiler judged that an unsigned int is _always_ greater or equal to 0, thus // there is no need to check the condition, just loop. // The same with raw CPU instructions, for no particular reason other than // to emphasize that instructions are of variable length on x86 and you can // see the immediates right there inside them, like 0x10 in the 3rd instruction, // 0x0a 0x00 0x00 0x00 in the 4th one, 01 in the 5th, and also -4s (-4 is encoded // as hex 0xfc, 2's complement in 8 bits) in the 4th and the 5th, and so on. // See if you can find them all :) // // Also note 0x90, the encoding of NOP. Why is it there? // (gdb) disas /r main Dump of assembler code for function main: 0x0000000000001139 <+0>: 55 push %rbp 0x000000000000113a <+1>: 48 89 e5 mov %rsp,%rbp 0x000000000000113d <+4>: 48 83 ec 10 sub $0x10,%rsp 0x0000000000001141 <+8>: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) 0x0000000000001148 <+15>: 83 6d fc 01 subl $0x1,-0x4(%rbp) 0x000000000000114c <+19>: 48 8d 05 b1 0e 00 00 lea 0xeb1(%rip),%rax # 0x2004 0x0000000000001153 <+26>: 48 89 c7 mov %rax,%rdi 0x0000000000001156 <+29>: e8 d5 fe ff ff call 0x1030 0x000000000000115b <+34>: 90 nop 0x000000000000115c <+35>: eb ea jmp 0x1148 End of assembler dump. // With more optimization, the compiler will eliminate even more, the variable "i" itself: [sergey@thepond cs59]$ gcc -Wall -O2 -o hello-loop-uint-O2 hello-loop-uint.c [sergey@thepond cs59]$ gdb ./hello-loop-uint-O2 GNU gdb (GDB) 17.2 Copyright (C) 2025 Free Software Foundation, Inc. [..skipped..] (No debugging symbols found in ./hello-loop-uint-O2) (gdb) disas main Dump of assembler code for function main: 0x0000000000001040 <+0>: sub $0x8,%rsp 0x0000000000001044 <+4>: nop 0x0000000000001045 <+5>: data16 cs nopw 0x0(%rax,%rax,1) // <-- really long NOP (see below) 0x0000000000001050 <+16>: lea 0xfad(%rip),%rdi # 0x2004 <------------------+ 0x0000000000001057 <+23>: call 0x1030 | 0x000000000000105c <+28>: jmp 0x1050 // <-- unconditional tight loop ---+ End of assembler dump. (gdb) disas /r main Dump of assembler code for function main: 0x0000000000001040 <+0>: 48 83 ec 08 sub $0x8,%rsp 0x0000000000001044 <+4>: 90 nop 0x0000000000001045 <+5>: 66 66 2e 0f 1f 84 00 00 00 00 00 data16 cs nopw 0x0(%rax,%rax,1) 0x0000000000001050 <+16>: 48 8d 3d ad 0f 00 00 lea 0xfad(%rip),%rdi # 0x2004 0x0000000000001057 <+23>: e8 d4 ff ff ff call 0x1030 0x000000000000105c <+28>: eb f2 jmp 0x1050 End of assembler dump. (gdb)