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
。因此,基于这个实验,我有几个问题希望有人比我更有经验来回答。
- 这是我没有提供必要的编译标志来执行 'strict' c98 标准的问题吗?
- 这是我误解了文档的情况,还是我应该参考一些 gcc 本身的文档?如果是这样,我在哪里可以找到它?
- compiling/linking 过程中有什么我不理解的基本原理可以解释这个问题吗?
- 为什么我会被警告不兼容,甚至警告我调用的函数不存在,但代码仍然可以正常工作?
- 这个实验是否意味着
-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
选项,但显然有些勉强,正如您从贬义名称中可以看出的那样。
我一直在阅读 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
。因此,基于这个实验,我有几个问题希望有人比我更有经验来回答。
- 这是我没有提供必要的编译标志来执行 'strict' c98 标准的问题吗?
- 这是我误解了文档的情况,还是我应该参考一些 gcc 本身的文档?如果是这样,我在哪里可以找到它?
- compiling/linking 过程中有什么我不理解的基本原理可以解释这个问题吗?
- 为什么我会被警告不兼容,甚至警告我调用的函数不存在,但代码仍然可以正常工作?
- 这个实验是否意味着
-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
选项,但显然有些勉强,正如您从贬义名称中可以看出的那样。