Search code examples
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:

    • You need to edit the sources to insert the #pragma after all standard headers are included. (A problem arises if these inclusions are anywhere other than the beginning of the source file, as you need to poison the identifiers for the user source code and not for the standard headers.)
    • A strictly conforming C 1990 program may use int64_t and other such identifiers provided it defines them itself, since these were ordinary identifiers in C 1990. If that is an issue for you, then the #define method above allows that to work, whereas the poison method does not.