Unexpected stuff!

Let’s have a look at this code:

int main()
{
    for(int i{0};i<500;i++)
    {
        std::cout<<"This is a big number: "<<i*20000000<<std::endl;
    }
}

And let’s compile it with g++ using the flag -O3, what’s going to happen?

Before answering, let’s have a look at the assembly, shall we?

L16:
	movsbl	39(%ebx), %eax
L6:
	movl	%esi, %ecx
	movl	%eax, (%esp)
	addl	$20000000, %edi
	call	__ZNSo3putEc
	subl	$4, %esp
	movl	%eax, %ecx
	call	__ZNSo5flushEv
L4:
	movl	$22, 8(%esp)
	movl	$LC2, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
	movl	%edi, (%esp)
	movl	$__ZSt4cout, %ecx
	call	__ZNSolsEi
	movl	%eax, %esi
	movl	(%eax), %eax
	subl	$4, %esp
	movl	-12(%eax), %eax
	movl	124(%esi,%eax), %ebx
	testl	%ebx, %ebx
	je	L15
	cmpb	$0, 28(%ebx)
	jne	L16
	movl	%ebx, %ecx
	call	__ZNKSt5ctypeIcE13_M_widen_initEv
	movl	(%ebx), %eax
	movl	24(%eax), %edx
	movl	$10, %eax
	cmpl	$__ZNKSt5ctypeIcE8do_widenEc, %edx
	je	L6
	movl	$10, (%esp)
	movl	%ebx, %ecx
	call	*%edx
	subl	$4, %esp
	movsbl	%al, %eax
	jmp	L6

L15:
	call	__ZSt16__throw_bad_castv
	.cfi_endproc

This is actually only a portion of the whole assembly, what I care to show you is the main loop and what’s going on, in the code you can skip the process of calling cout which is somwehere between L4 and the call to L6, and just have a look at the looping condition, there’s something weird there!

Note that jump to L15 is not going to happen (in this particular piece of code), and both jumps to L6 and L16 are not terminating the loop, so where is the loop end condition then?

Well, there’s no end condition, this code will print numbers forever!

If i change the code this way:

int main()
{
    for(int i{0};i<500;i++)
    {
        std::cout<<"This is a big number: "<<i*200<<std::endl;
    }
}

The assembler output will be:

L15:
	movsbl	39(%ebx), %eax
L6:
	movl	%edi, %ecx
	movl	%eax, (%esp)
	addl	$200, %esi
	call	__ZNSo3putEc
	subl	$4, %esp
	movl	%eax, %ecx
	call	__ZNSo5flushEv
	cmpl	$100000, %esi //Check whether i==100000, which is 200*500
	je	L13           //If this is the case, quit the loop
L7:
	movl	$22, 8(%esp)
	movl	$LC2, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
	movl	%esi, (%esp)
	movl	$__ZSt4cout, %ecx
	call	__ZNSolsEi
	movl	%eax, %edi
	movl	(%eax), %eax
	subl	$4, %esp
	movl	-12(%eax), %eax
	movl	124(%edi,%eax), %ebx
	testl	%ebx, %ebx
	je	L14
	cmpb	$0, 28(%ebx)
	jne	L15
	movl	%ebx, %ecx
	call	__ZNKSt5ctypeIcE13_M_widen_initEv
	movl	(%ebx), %eax
	movl	24(%eax), %edx
	movl	$10, %eax
	cmpl	$__ZNKSt5ctypeIcE8do_widenEc, %edx
	je	L6
	movl	$10, (%esp)
	movl	%ebx, %ecx
	call	*%edx
	subl	$4, %esp
	movsbl	%al, %eax
	jmp	L6
	.p2align 4,,10
L13: //Terminate the loop, quit main
	leal	-16(%ebp), %esp
	xorl	%eax, %eax
	popl	%ecx
	.cfi_remember_state
	.cfi_restore 1
	.cfi_def_cfa 1, 0
	popl	%ebx
	.cfi_restore 3
	popl	%esi
	.cfi_restore 6
	popl	%edi
	.cfi_restore 7
	popl	%ebp
	.cfi_restore 5
	leal	-4(%ecx), %esp
	.cfi_def_cfa 4, 4
	ret
L14:
	.cfi_restore_state
	call	__ZSt16__throw_bad_castv
	.cfi_endproc

Most of the code is the same, but now there’s a termination condition and the loop properly exit when the 500 iterations are performed, so the output will be what we’re expecting.

The point of this post is simple, do not ever ignore the warnings your compiler is issuing, many of them may cause serious problems! In many real project indeed, warnings are considered second class citizen, and nobody care about fixing them on-the-fly!

This lead to compilation outputs with hundreds of warnings which from time to time are under the scope, and people are asked to clean the mess up, you know the corporate way: “Fix the warnings as ASAP as possible! Why nobody fixed them before??” said the manager which refused to let people fix them before, no time for that 🙂

Anyway, the problem in this code is the undefined behavior in the expression i*20000000, for i values starting from 108 the multiplication cause an integer overflow, modern compilers will infer that since there’s an undefined behavior which is an unacceptable thing, then it shouldn’t happen at all and the programmer must have taken actions for this situation, therefore it must be the case that i will never be greater than 107, thus the condition i<500 is always true.

Which lead to an infinite loop..

When compiling this code, the compiler will raise the following warning:

undefined_behaviour.cpp: In function 'int main()':
undefined_behaviour.cpp:7:48: warning: iteration 108u invokes undefined behavior 
[-Waggressive-loop-optimizations]
 std::cout<<"This is a big number: "<<i*20000000<<std::endl;
 ^
undefined_behaviour.cpp:5:5: note: containing loop
 for(int i{0};i<500;i++)
 ^

Which should give us some hint that something is going to be very wrong, we don’t want to have undefined behavior in our code, the fix for the problem (if you don’t want to fix the code) the just compile the program with the flag -fno-aggressive-loop-optimizations.

For details about the original source of information for this problem have a look at HERE, or the related thread on SO.

Always take care of your compiler warnings!

Thanks for reading!

Leave a Reply