overflow and compiler warnings

M

Mark

Hello

Given two code snippets both compiled with "gcc -ansi -pedantic -W -Wall":

(1)
#include <limits.h>
int main(void)
{
int i = INT_MAX;
i++; /* no warning */
return 0;
}

(2)
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX + 1; /* warning: integer overflow */
i++;
return 0;
}

Why does compiler not give any warning about overflow in the (1) case, while
it does in the second case.
 
P

Phil Carmody

Mark said:
Hello

Given two code snippets both compiled with "gcc -ansi -pedantic -W -Wall":

(1)
#include <limits.h>
int main(void)
{
int i = INT_MAX;
i++; /* no warning */
return 0;
}

(2)
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX + 1; /* warning: integer overflow */
i++;
return 0;
}

Why does compiler not give any warning about overflow in the (1) case,
while it does in the second case.

Statelessness.

Phil
 
K

Keith Thompson

Mark said:
Given two code snippets both compiled with "gcc -ansi -pedantic -W -Wall":

(1)
#include <limits.h>
int main(void)
{
int i = INT_MAX;
i++; /* no warning */
return 0;
}

(2)
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX + 1; /* warning: integer overflow */
i++;
return 0;
}

Why does compiler not give any warning about overflow in the (1) case,
while it does in the second case.

In the second example, the expression INT_MAX + 1 itself causes an
overflow. Both operands are compile-time constants, so it's easy for
the compiler to notice that the addition is going to overflow.

In the first, the expression that overflows is, in effect, i + 1. The
value of i cannot easily be determined at compile time.

On the other hand, it can be determined with some effort, using fairly
straightforward dataflow analysis. I wouldn't be surprised if gcc
does warn about it if you specify a higher level of optimization.

(I just tried it, and it doesn't warn, probably because it can
eliminate i altogether. But when I add a statement to print the value
of i, it *still* doesn't warn, and the generated code sets the value
of i to -2147483648. Perhaps there's another option that would
trigger the warning.)
 
B

Boon

Keith said:
In the first, the expression that overflows is, in effect, i + 1. The
value of i cannot easily be determined at compile time.

On the other hand, it can be determined with some effort, using fairly
straightforward dataflow analysis. I wouldn't be surprised if gcc
does warn about it if you specify a higher level of optimization.

The following options might be relevant.

-fstrict-overflow
-Wstrict-overflow=n
-ftrapv

I can't get gcc to warn either.

$ cat test.c
#include <limits.h>
int foo(void)
{
int i = INT_MAX;
i++; /* no warning */
return i;
}

$ gcc -std=c89 -pedantic -Wall -Wextra -O2 -Wstrict-overflow=5 -ftrapv
-fomit-frame-pointer -S test.c

$ cat test.s
[...]
_foo:
movl $-2147483648, %eax
ret
 
E

Eric Sosman

Mark said:
Hello

Given two code snippets both compiled with "gcc -ansi -pedantic -W -Wall":

(1)
#include <limits.h>
int main(void)
{
int i = INT_MAX;
i++; /* no warning */
return 0;
}

(2)
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX + 1; /* warning: integer overflow */
i++;
return 0;
}

Why does compiler not give any warning about overflow in the (1) case,
while it does in the second case.

The offending calculation in the second case happens
during compilation, while in the first case it doesn't occur
until run time.

(This is slightly loose language, because "formally" both
calculations occur at run time. But many compilers optimize
expressions involving constants by pre-calculating as much of
the expression as possible -- In a few cases they're required
to do compile-time calculations, so the machinery is already
there in the compiler. In your second example, the compiler
notices that the expression assigned to `i' consists entirely
of constants, so it tries to pre-calculate the result. In the
course of that calculation, it discovers the overflow.)
 
J

jacob navia

Mark said:
Hello

Given two code snippets both compiled with "gcc -ansi -pedantic -W -Wall":

(1)
#include <limits.h>
int main(void)
{
int i = INT_MAX;
i++; /* no warning */
return 0;
}

(2)
#include <limits.h>
int main(void)
{
int i;
i = INT_MAX + 1; /* warning: integer overflow */
i++;
return 0;
}

Why does compiler not give any warning about overflow in the (1) case,
while it does in the second case.

The lcc-win compiler will detect the overflow at run time if you compile
with the -overflowcheck flag.

As to why the difference in gcc's behavior the expression
INT_MAX+1
will be calculated at compile time and the compiler then discovers
the overflow. The second expression will not be calculated at compile time.
 
M

Mark

Eric Sosman said:
The offending calculation in the second case happens
during compilation, while in the first case it doesn't occur
until run time.

Thanks to everybody for valuable comments. I have two more questions:
(1) is this a conversion rule (6.3.1.3 of the C99 standard) which is applied
to assignment to 'i' in both examples?
(2) what rules do programmers use to avoid overflows in their programs? Are
there any rules-of-thumb to remember, that can decrease any probabilities of
overflows in code?

Thanks.
 
J

jacob navia

Mark said:
Thanks to everybody for valuable comments. I have two more questions:
(1) is this a conversion rule (6.3.1.3 of the C99 standard) which is
applied to assignment to 'i' in both examples?
(2) what rules do programmers use to avoid overflows in their programs?
Are there any rules-of-thumb to remember, that can decrease any
probabilities of overflows in code?

Thanks.

To (2):

As I told you in another message,
(a): the compiler can emit code to check all operations that could
overflow. This has the advantage that needs no modifications to your
code, but slows down the generated code a bit.
(b) You could use a pseudo function (intrinsic) to test for overflow
within some context. This has the advantage of testing only
in the context where you know it is needed, but requires code
modifications and could miss some overflows.

The lcc-win compiler proposes both solutions. I do not know why other
compilers do not propose similar facilities. The code generation is
quite trivial.
 
B

Boon

Jacob said:
(a): the compiler can emit code to check all operations that could
overflow. This has the advantage that needs no modifications to your
code, but slows down the generated code a bit.
(b) You could use a pseudo function (intrinsic) to test for overflow
within some context. This has the advantage of testing only
in the context where you know it is needed, but requires code
modifications and could miss some overflows.

The lcc-win compiler proposes both solutions. I do not know why other
compilers do not propose similar facilities. The code generation is
quite trivial.

http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Code-Gen-Options.html

3.18 Options for Code Generation Conventions

These machine-independent options control the interface conventions
used in code generation.

-ftrapv
This option generates traps for signed overflow on addition,
subtraction, multiplication operations.

$ cat foo.c
int mymul(int a)
{
return a*2;
}

$ gcc -O2 -ftrapv -S foo.c

_mymul:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl 8(%ebp), %eax
movl $2, 4(%esp)
movl %eax, (%esp)
call ___mulvsi3
leave
ret
 
B

Ben Bacarisse

Mark said:
Would it be if the code was:
int i = INT_MAX;
int j = i * 2;

Everything here is of type int. The section you refer to (now
snipped) covers conversion between signed and unsigned types.
 
J

jacob navia

Boon said:
$ gcc -O2 -ftrapv -S foo.c

_mymul:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl 8(%ebp), %eax
movl $2, 4(%esp)
movl %eax, (%esp)
call ___mulvsi3
leave
ret

That is a joke. Completely unusable.
 
J

jacob navia

Boon said:
*What* is a joke? *What* is completely unusable?
(There is no room for hand-waving in a technical forum.)

How does your implementation check for signed overflow on multiplication?

lcc-win generates for the "mymult" function this code:
; 1 int mymul(int a)
.data
mymul__labelname:
; "mymul\x0"
.byte 109,121,109,117,108,0
.text
_mymul:
; 2 {
; 3 return a*2;
imul $2,4(%esp),%eax
jno _$L2
pusha
pushl $3
pushl $mymul__labelname
call __overflow
addl $8,%esp
popa
_$L2:
; 4 }
ret

In the normal case the (no overflow) the only cost to the user is
a mispredicted forward branch, i.e. 1 instruction.

The cost for gcc is:
(1) Push the arguments (2 instructions)
(2) Make a call (1 instruction)
(3) Adjust the stack (1 instruction)

The "mulvsi3" function is even worst. Since it is a machine independent
function it tests for overflow by checking the result instead of using
the overflow flag. In 64 bit linux is 11 instructions!
So we have for gcc a total cost of 11+1+1+2-->15 instructions.

Note that under lcc-win you can define a function called _overflow()
that will replace the library function with your own function that can
take *other* measures than just aborting the program. It will receive
2 arguments:

o the file name and
o the line number
where the overflow occurred.

You can call abort() in your function, or make a longjmp somewhere,
or ignore the overflow and return.

Gcc will always call abort() without any hint as to *where* in your code
the overflow occurred, so that you have no clue as to what went wrong.
Imagine the pleasure of getting a call from your client:
"Your program aborted without warning! I lost all I was doing you
stupid!"
And you have no clue as to what caused the abort() call!

jacob
 
M

Mark

pete said:
No.

(INT_MAX) /* type int */
(INT_MAX + 1) /* type int, undefined value */
(INT_MAX + 1u) /* type unsigned */
((unsigned)INT_MAX + 1) /* type unsigned */

Ok, I think I got it, this is called "integral promotion" not a
"conversion". As long as we have integral operands, they will be promoted to
some common type. Is this correct?
 
M

Mark

pete said:

I'm confused. Consider the following:

(INT_MAX + 1u)

There are two operands in this addition operation, one is 'int' and the
other is 'unsigned int'. According to 6.3.1.1p2:

If an int can represent all values of the original type, the value is
converted to an int;
otherwise, it is converted to an unsigned int. These are called the integer
promotions.48) All other types are unchanged by the integer promotions.
 
P

Peter Nilsson

Mark said:
...
I'm confused. Consider the following:

(INT_MAX + 1u)

There are two operands in this addition operation, one
is 'int' and the other is 'unsigned int'. According to
6.3.1.1p2:

If an int can represent all values of the original type,
the value is converted to an int; otherwise, it is
converted to an unsigned int. These are called the
integer promotions.48) All other types are unchanged by
the integer promotions.

That only applies to expressions with rank less than int.
INT_MAX has type int, 1u has type unsigned int.

You need to look at usual arithmetic conversions (6.3.1.8).

...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.

Hence, INT_MAX above is converted to an unsigned int and
the addition occurs with unsigned int semantics.
 
K

Keith Thompson

Mark said:
I'm confused. Consider the following:

(INT_MAX + 1u)

There are two operands in this addition operation, one is 'int' and
the other is 'unsigned int'. According to 6.3.1.1p2:

If an int can represent all values of the original type, the value
is converted to an int; otherwise, it is converted to an unsigned
int. These are called the integer promotions.48) All other types are
unchanged by the integer promotions.

Right, and no integer promotions occur in the expression (INT_MAX +
1u). The integer promotions convert types narrower than int and
unsigned int to int or to unsigned int. Both operands are already
wide enough that the integer promotions don't apply.

The conversion that happens here is a result of the "usual arithmetic
conversions" (C99 6.3.1.8), not the "integer promotions". 6.5.6,
"Additive operators", specifies that the usual arithmetic conversions
are applied to the operands of "+".

Also, both the integer promotions and the usual arithmetic conversions
*are* conversions.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,994
Messages
2,570,222
Members
46,809
Latest member
moe77

Latest Threads

Top