strtoull() 在 C89 中的可用性

strtoull() Availbility in C89

我一直在阅读 here 的 strtoul()/strtoull() 文档,在“符合”部分的底部,它提出了以下两点:

strtoul(): POSIX.1-2001, POSIX.1-2008, C89, C99 SVr4.
strtoull(): POSIX.1-2001, POSIX.1-2008, C99.

这两行以及整个文档中的其他引用向我表明,在使用 c89/c90 标准编译程序时,函数 strtoull 不应该可用。但是,当我 运行 使用 gcc 进行快速测试时,它允许我调用此函数,而不管我指定的标准如何。

首先,我用来测试的代码:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    unsigned long long x;
    const char *str = "1234";

    x = strtoull(str, NULL, 10);

    printf("%llu\n", x);

    return 0;
}

这是我的编译命令:

gcc test.c -std=c89 -pedantic -Wall -Wextra

现在,公平地说,它确实警告我兼容性问题:

test.c: In function ‘main’:
test.c:6:16: warning: ISO C90 does not support ‘long long’ [-Wlong-long]
  unsigned long long x;
                ^~~~
test.c:9:6: warning: implicit declaration of function ‘strtoull’; did you mean ‘strtoul’? [-Wimplicit-function-declaration]
  x = strtoull(str, NULL, 10);
      ^~~~~~~~
      strtoul
test.c:11:9: warning: ISO C90 does not support the ‘ll’ gnu_printf length modifier [-Wformat=]
  printf("%llu\n", x);
         ^~~~~~~~

这些警告消息正是我所期望的文档。提示找不到我指定的函数,而且C90标准也不支持unsigned long long。但是,当我尝试 运行 这段代码时,它工作得很好,没有崩溃或其他类型的错误。它根据需要打印值 1234。因此,基于这个实验,我有几个问题希望有人比我更有经验来回答。

  1. 这是我没有提供必要的编译标志来执行 'strict' c98 标准的问题吗?
  2. 这是我误解了文档的情况,还是我应该参考一些 gcc 本身的文档?如果是这样,我在哪里可以找到它?
  3. compiling/linking 过程中有什么我不理解的基本原理可以解释这个问题吗?
  4. 为什么我会被警告不兼容,甚至警告我调用的函数不存在,但代码仍然可以正常工作?
  5. 这个实验是否意味着 -std=c89 -pedantic 标志实际上并没有强制执行 C89/C90 标准?

最后一点,我并不是想说我想在 C89 中使用这个函数,我只是对兼容性限制感到好奇,然后对答案感到困惑。

提前感谢您的任何回复!

从 C89/C90 编译器的角度来看,您的代码唯一的错误是使用了 unsigned long long,这看起来像是一个语法错误。该标准仅要求编译器在这种情况下产生“诊断”,而 GCC 已通过其“ISO C90 不支持 long long”警告这样做。没有要求这个错误应该是致命的,如果需要,编译器可以决定以其他方式处理代码。 GCC 显然选择将其理解为它支持的 long long 类型,作为对 C89 的扩展。

strtoull 的使用看起来就像是您编写的某个函数,因为 C89 无法知道此名称在未来的某些标准版本中会很特殊。 (好吧,他们确实指定了以后可以将更多以 str 开头的函数添加到 <string.h> 中,但这并不会使您的代码对于 C89 是非法的。)您还没有声明它,但是C89 允许隐式声明,因此可以理解为声明为 int strtoull();,即返回 int 并带有未指定的参数。据我所知,隐式声明不需要诊断,但 GCC 还是选择发布一个。因此它被视为对该源文件中未定义的函数的任何其他调用,并且编译器假定您程序的其他部分(包括您使用的库)将定义它。

事实上,您的程序的其他部分确实定义了它,即 libc,因为您的 libc 符合 C99 及更高版本。 (你知道,希望 libc 不是 GCC 的一部分。)C 库作者通常不提供仅包含特定标准版本函数的库版本,因为周围有这么多不同的库会很尴尬且效率低下.所以链接成功。

但是请注意,由于隐式声明,程序可能无法正确运行。编译器将错误地生成代码,假设 strtoull returns int,这取决于您系统的调用约定,可能会导致各种问题。在 x86-64 上,这意味着您的程序将只查看结果的低 32 位并将它们符号扩展为 64 位。因此,如果您尝试转换适合 32 位但不适合 long long 的数字,您将得到错误的结果。 Example.

如果您想要一个可以在 支持 C89 的系统上运行的程序,您有责任查看编译器发出的诊断并修复相应的问题问题。评论中提到的 -pedantic-errors 选项可以帮助解决这个问题,因为它会在发出此类诊断时导致编译失败。

如果您能找到一个仅支持 C89 的 libc 也会有所帮助,但这不是 GCC 的问题。但是它的隐式声明警告确实给了你一些帮助,让你注意到你调用了一个你可能不打算让你的程序定义的函数。

最后一点,从历史上看,GCC 的设计理念的一部分就是他们认为“执行标准”并不是他们真正想要做的事情的一部分。他们认为自己的目标是编写一个 compiler 来帮助人们编写和编译有用的程序,而不是 linter 检查是否符合编码标准;他们认为后者应该是一个单独的项目,而不是他们感兴趣的项目。因此,他们在为标准语言提供扩展方面很自由,而不是特别努力地为程序提供避免使用它们的方法。他们确实提供了 -pedantic 选项,但显然有些勉强,正如您从贬义名称中可以看出的那样。