For example:
I would like to have a compiler warning in this case.
Is this possible?
#include <stdio.h>
int main(void)
{
char i;
int count = 555;
for(i = 0; i < count; i++)
printf("%d\n", i);
return 0;
}
For example:
I would like to have a compiler warning in this case.
Is this possible?
#include <stdio.h>
int main(void)
{
char i;
int count = 555;
for(i = 0; i < count; i++)
printf("%d\n", i);
return 0;
}
Since the core issue here is integer overflow/assignment out of range. In case char
is signed, it's run-time implemention-defined or undefined behavior and not really the compiler's business. It is (unfortunately) in the end the C programmer's responsibility to know and find all forms of poorly defined behavior in the code.
Compilers also tend to have poor diagnostic capabilities, their job is to check that your code is valid C and inform you if it isn't. If you are lucky they might also give you a head's up about common bugs and undefined behavior, but that's nothing you can count on, it's just a bonus.
Regarding implicit promotions, there is -Wconversion
in gcc but this option is very shaky and unreliable. It does not give a warning in your case and in other cases it gives false warnings.
The best option is to use an external tool known as "static analyzer". They are similar to compilers but focus on finding questionable code and bugs. There's a few open source ones like clang-tidy and Frama-C, but most such tools are commercial.
One flavour of such static analyzers are "MISRA checkers" that are used to verify compliance with the MISRA C guidelines. A significant part of MISRA C is devoted to finding implicit promotion/accidental type change bugs, so in this case I would recommend to get a MISRA C static analyzer.
Is there a way to get warning on integer promotion?
OP's concerns are real yet misplaced on integer promotion.
Compiler options exist to help OP, yet they can be selective in their occurrence.
i++
is like i = i + 1
(but i
only evaluated once) and with an 8-bit i
, the promotion from char
to int
is well defined before the addition. The addition is well defined and within range too.
The assignment of an int
to a char
with a value that is out-of-range is a effectively an integer demotion and a legitimate concern for OP. Some compilers provide options to catch some of these. "Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised." applies.
char i;
// warning: conversion from 'int' to 'char' may change value [-Wconversion]
i = rand();
// But not here
i++;
i = i + 1;
i = (int) (i + 1);
With printf("%d\n", i);
, char i
is silently promoted to int
as part of a ...
argument.
There is no concern here about the value.
i < count
is always true. The integer promotion is not the issue, but that i < count
is never false.
Compilers offer some options like below that warn sometimes. Code analysis tools offer additional warnings.
// warning: comparison is always true due to limited range of data type [-Wtype-limits]
for(i = 0; i <= (char) 127; i = i + 1)
// No warning.
for(i = 0; i <= count; i = i + 1)
// No warning.
char b = 127;
for(i = 0; i <= b; i = i + 1)
// No warning and no integer promotion
int m = INT_MAX;
for(int i = 0; i <= m; i = i + 1)
Note: there is no signed integer overflow here nor undefined behavior. There is implementation-defined behavior.
The only warning you can get with clang -Wall -pedantic
has nothing to do with the real issue in the code:
<source>:3:9: error: a function declaration without a prototype is deprecated in all versions of C [-Werror,-Wstrict-prototypes]
int main()
^
void
For some reason, neither gcc nor clang report the infinite loop caused by the limited range of the i
variable, which is odd because they do detect it and take advantage of it:
gcc 12.2 -O3
assembly code:
.LC0:
.string "%d\n"
main:
push rbx
xor ebx, ebx
.L2:
movsx esi, bl
mov edi, OFFSET FLAT:.LC0
xor eax, eax
add ebx, 1
call printf
jmp .L2
clang 15.0 -O3
assembly code:
main:
push rbp
push rbx
push rax
lea rbx, [rip + .L.str]
xor ebp, ebp
.LBB0_1: # =>This Inner Loop Header: Depth=1
movsx ebp, bpl
mov rdi, rbx
mov esi, ebp
xor eax, eax
call printf@PLT
inc bpl
jmp .LBB0_1
.L.str:
.asciz "%d\n"
This a simple and telling example of insane optimisation: both compilers detect an infinite loop that is clearly unintended and instead of reporting this find to the programmer, they bluntly generate the runaway code. Shame on them!