Search code examples
cwarnings

How urgent should warnings be treated in C?


I don't know where I got this habit from, but normally I treat compiler warnings as errors. I know the program will still run but warnings bother me. The one that I think means nothing to me is

Narrowing conversion from <data type> to <data type> is implementation-defined.

I still try to fix it though. So I came here to ask, how urgent should compiler warnings be treated? I'm pretty sure a lot of them will vary so can someone tell me which ones to look out for?


Solution

  • Due to long history the C compilers tend to issue warnings for many issues that are considered errors in any other languages. The C standard explicitly mentions this as a possible outcome in C11/C18 5.1.1.3:

    5.1.1.3 Diagnostics

    1. A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined. Diagnostic messages need not be produced in other circumstances. 9)

    2. EXAMPLE An implementation shall issue a diagnostic for the translation unit:

        char i;
        int i;
      

      because in those cases where wording in this International Standard describes the behavior for a construct as being both a constraint error and resulting in undefined behavior, the constraint error shall be diagnosed.

    Footnotes

    1. The intent is that an implementation should identify the nature of, and where possible localize, each violation. Of course, an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated. It may also successfully translate an invalid program.

    (emphasis mine)

    Therefore the mere fact that a program successfully compiles cannot be taken as a proof that it is not doing something profoundly wrong in C. For example many undefined behaviours tend to change behaviour on the same compiler depending on the command-line switches.

    The C compilers are still so conservative that any warnings issued by default should be taken seriously and investigated thoroughly.

    Not all diagnostics are enabled by default. GCC for example has two broad category switches:

    -Wall

    This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros. This also enables some language-specific warnings described in C++ Dialect Options and Objective-C and Objective-C++

    -Wextra

    This enables some extra warning flags that are not enabled by -Wall.

    As -Wall mentions the questionable constructions that it diagnoses would be easy to avoid or modify. -Wextra adds diagnostics for more stylistic issues like "unused variable", which, from time to time do highlight real issues in your code - perhaps you were supposed to use that variable j but typoed that in the code, or forgot to multiply by it...

    My subjective, yet expert, opinion is that if

    • The compiler emits warnings even without -Wall -Wextra or specific warning options, I would not even run it for debugging purposes.

    • I would not put the code into production use without analysing each of the warnings produced by -Wall -Wextra. Of course this process is easier if you fix these issues as the number of diagnostic messages will drop.


    In the case of

    Narrowing conversion from to is implementation-defined.

    this diagnostic message is very likely not enabled by default. It tells that an operation like

    int i;
    float f = i;
    

    is attempted and that the conversion shall vary in behaviour from implementation to another. Usually implementation means (compiler, operating system, processor) - so if you have consulted the compiler manuals and found the implementation-defined behaviour documented there acceptable, then it of course is acceptable to suppress it one way or another.

    If for now you target the same operating system, using the same compiler or a compiler that has similar behaviour (GCC/Clang) and the same processor family, it is still probably better just to suppress these diagnostics of implementation-defined behaviour with a command-line switch rather than adding casts in the code to just suppress a possible portability issue. That way if you need to review these for porting to a new implementation, you can re-enable the warning.


    On the other hand, there are warnings that signal about undefined behaviours:

    x.c: In function ‘main’:
    x.c:5:22: warning: right shift count >= width of type
            [-Wshift-count-overflow]
        5 |     printf("%d\n", a >> 33);
          |                      ^~
    

    or

    x.c: warning: iteration 2147483647 invokes undefined behavior
            [-Waggressive-loop-optimizations]
        8 |     for (i = 0; i >= 0; i++) { }
          |                         ~^~
    

    (will be diagnosed by GCC only when optimizations are enabled)

    or

    x.c:5:14: warning: format ‘%f’ expects argument of type ‘double’,
          but argument 2 has type ‘int’ [-Wformat=]
    5 |     printf("%f\n", i);
      |             ~^     ~
      |              |     |
      |              |     int
      |              double
      |             %d
    

    These are really telling of serious coding errors whose effects can be really hard to debug - and the behaviour of the same construct can vary even within the same compiled executable, not to mention different processors. Personally, I would not even bother to run the program even for testing purposes until I would have fixed these.