首页 > 解决方案 > strtoull() 在 C89 中的可用性

问题描述

我从这里开始阅读 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. 但是,当我尝试运行此代码时,它运行良好,没有崩溃或其他类型的错误。它根据需要打印 value 1234。所以,基于这个实验,我有几个问题我希望有人比我更有经验。

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

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

提前感谢您的任何回复!

标签: cgccc99c89strtoull

解决方案


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

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

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

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

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

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

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


推荐阅读