cgcccompiler-errorsclangc89

Compiler doesn't issue warning on `uint64_t` under strict C89 mode


I'm trying to write strict ISO C89-compliant code. Since long long is not standard and is often implemented as compiler extensions before C99, the compiler should warn me it when I use it. However, when using either gcc or clang, there's no warning when int64_t is used and expands to long long (with 32-bit compilation -m32). Is there any way to make the compiler warn me?

For example:

/* test.c */
#include <stdint.h>
#include <stdio.h>
int main(void) {
    printf("The size of an int64_t is %u.\n", (unsigned)sizeof(int64_t));
    return 0;
}

Compiled with clang or gcc:

clang-17 -m32 -std=c89 -Wall -Wextra -Werror -pedantic -pedantic-errors test.c
# or
x86_64-pc-linux-gnu-gcc-13.2.1 -m32 -std=c89 -Wall -Wextra -Werror -pedantic -pedantic-errors test.c

They issue no warning or error, even if the int64_t is actually a typedef to long long int, since clang-17 -m32 -std=c89 -E -dD -Wall -Wextra -Werror -pedantic -pedantic-errors test.c gives me:

...
#define __INT64_TYPE__ long long int
#define __INT64_FMTd__ "lld"
#define __INT64_FMTi__ "lli"
#define __INT64_C_SUFFIX__ LL
...

and in /path/to/clang/17/include/stdint.h there's

...
#ifdef __INT64_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/
typedef __INT64_TYPE__ int64_t;
# endif /* __int8_t_defined */
typedef __UINT64_TYPE__ uint64_t;
# undef __int_least64_t
...

But if I replace int64_t with long long or __INT64_TYPE__ in the above test.c the compiler will complain about it. So why there's a behavioral difference between long long and uint64_t?

The most relevant question on SO seems to be this and this, but their answers don't seem to explain why there's no warning when using -m32. (In -m64 mode uint64_t is not a problem since it expands to long, which is in C89 standard).

-- EDIT --

Eric's answer explained nicely why and how compilers treat system headers and user headers differently. For those who might be interested, in addition to creating your own sets of headers, I found one way to make the compiler also check C-standard compliance for system headers:

  1. Use clang -v <args>... to generate the command that clang is actually running;
  2. Replace all -internal-isystem with -I;
  3. Run the modified command, then clang will output:
In file included from test.c:2:
/usr/lib/clang/17/include/stdint.h:10:1: error: // comments are not allowed in this language [-Werror,-Wcomment]
   10 | // AIX system headers need stdint.h to be re-enterable while _STD_TYPES_T
      | ^
/usr/lib/clang/17/include/stdint.h:52:3: error: #include_next is a language extension [-Werror,-Wgnu-include-next]
   52 | # include_next <stdint.h>
      |   ^
2 errors generated.

meaning that the standard library is not C89-compliant, which is the expected behavior.


Solution

  • The most relevant question on SO seems to be this and this, but their answers don't seem to explain why there's no warning when using -m32.

    GCC would issue a warning on the typedef (rather than where the defined type is used), but GCC has built-in behavior to suppress warnings in system headers. So does Clang.

    A potential workaround is to make your own versions of system headers that suppress the undesired type definitions. For each standard header, make a file with this template:

    #define int64_t  DoNotUseint64_t
    #define uint64_t DoNotUseuint64_t
    #include <absolute path to actual system header>
    #undef  uint64_t
    #undef  int64_t
    

    Put those files in a directory, and compile with -isystem ThatDirectory.

    (Note you must do this for each standard header, because standard headers are allowed to include and define others even if the C standard does not explicitly say so. E.g., on my platform, including <stdio.h> defines int64_t.)

    This will result in no typedef being made for int64_t (it will be made for DoNotUseint64_t instead), so your program will get a compiler error if it attempts to use int64_t. There is some chance these redefinitions will break something in the standard headers, but you will have to try it and see.

    As Lundin notes, you could use #pragma GCC poison int64_t to flag uses of int64_t and other types. Some consideration of this versus the #define method above: