2

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;
}
zwol
  • 135,547
  • 38
  • 252
  • 361
manish ma
  • 1,706
  • 1
  • 14
  • 19
  • @VladfromMoscow I'd expect it to warn about a condition that is always true TBH, but apparently `gcc` doesn't give it... – Eugene Sh. Feb 22 '23 at 14:54
  • 4
    There are a few problems here that play together... The first is that smaller integer types (like `char` and `short`) will undergo *integer promotion* when used in any arithmetic expression (and comparison is an arithmetic expression). This is part of the base C language. Therefore the comparison will be done between two `int` values, which is perfectly fine. Another problem is that `char` might be *signed*, it's defined by the compiler. And signed integer overflow leads to *undefined behavior*. – Some programmer dude Feb 22 '23 at 14:54
  • @Someprogrammerdude is there a way for the compiler to detect there is a potential issue here and provide some warning? – manish ma Feb 22 '23 at 14:57
  • 1
    Unfortunately there doesn't seem to be any [warning option](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) for integer promotion, only `float` to `double` promotion. – Some programmer dude Feb 22 '23 at 14:58
  • no compile-time solutions [here](https://stackoverflow.com/questions/69104632/compiler-flags-for-checking-integer-overflow) either – yano Feb 22 '23 at 15:02
  • it will only warn if there is constant expression conversion. – 0___________ Feb 22 '23 at 15:07
  • You might get some luck with some static analyzers. `cppcheck` doesn't help though. – Eugene Sh. Feb 22 '23 at 15:07
  • 1
    `clang -Weverything` doesn't give anything, so clang has no warning, and that usually means gcc doesn't either. – Nate Eldredge Feb 22 '23 at 15:13
  • @EugeneSh.: If you write `i < 555` you do get a warning. By its code gen, the compiler clearly does know that `count` is constant and the comparison is always true (it optimizes it out) but yeah, unfortunately you don't get a warning from that. – Nate Eldredge Feb 22 '23 at 15:19
  • 1
    @Someprogrammerdude "And signed integer overflow leads to undefined behavior." --> there is no overflow here. – chux - Reinstate Monica Feb 22 '23 at 15:25
  • 2
    A commercial tool for static code analysis reports errors like "Infinite loop identified.", "The operands of this relational operator are expressions of different 'essential type' categories (char and signed).", "The value of this loop controlling expression is always 'true'.", "Definite: Implicit conversion to a signed integer type of insufficient size." – Bodo Feb 22 '23 at 16:00

3 Answers3

1

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • There is no `int` overflow with an 8-bit `char` - signed or unsigned in `i++`. There is the issue of assigning a value to `char` outside its range, by that is not UB. – chux - Reinstate Monica Feb 22 '23 at 15:22
  • @chux-ReinstateMonica Are you sure? Promotion does not happen in case of `i++`. `++` is analogous to `i += 1`, not to `i = i + 1` (`i` is only evaluated once). If `char` is signed and of value 127 `i++` invokes integer overflow and undefined behavior. – Lundin Feb 22 '23 at 15:35
  • In C, there is no `char`, `short` addition. Addition only happens between common types. `i + 1` is a `char + int` --> `promoted_char_to_int + int` --> `int + int`. – chux - Reinstate Monica Feb 22 '23 at 15:38
  • @chux-ReinstateMonica 6.5.2.4 postfix ++ does not mention promotion. They send you on "See the discussions of additive operators and compound assignment for information on constraints, types, and conversions and the effects of operations on pointers." Similarly unary prefix ++ says "The expression ++E is equivalent to (E+=1)." Checking compound assignment "A compound assignment of the form E1 op= E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once". Where does the promotion happen? – Lundin Feb 22 '23 at 15:42
  • 1
    Additive operators: "If both operands have arithmetic type, the usual arithmetic conversions are performed on them." – chux - Reinstate Monica Feb 22 '23 at 15:44
  • @chux-ReinstateMonica Not really convinced but that's a separate discussion. I changed the answer slightly. – Lundin Feb 22 '23 at 15:48
1

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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • *OP's concerns are misplaced.* The concerns are very valid. There is an expectation that the execution will stop after 555 iterations, but the actual behavior is an infinite loop because of undetected bug. – Eugene Sh. Feb 22 '23 at 15:48
  • @EugeneSh. Agree about _real_, yet still misplaced. Answer already went into real-ness with "an integer demotion and a legitimate concern for OP" – chux - Reinstate Monica Feb 22 '23 at 15:48
0

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!

chqrlie
  • 131,814
  • 10
  • 121
  • 189