显示,如果你让编译器推断这并不总是声明函数返回一个int
只会编译代码。这在C89/C90中是有效的,但是过时了; C99和C11要求在使用它们之前声明函数。 GCC版本5.1.0之前的GCC默认采用C90模式;您必须打开“拒绝此代码”警告。 GCC 5.1.0及更高版本默认采用C11。即使没有任何编译选项,您至少也会从代码中获取警告。
代码会正常链接,因为函数名称是strerror()
,无论它是否声明,链接器都可以在标准C库中找到该函数。通常,标准C库中的所有函数都可以自动用于链接 - 实际上,通常还有许多不那么标准的函数可用。 C没有像C++那样的类型安全链接(但是C++也坚持要在使用它之前声明每个函数,所以代码不会像C++那样在没有头的情况下编译)。
由于历史原因,数学库是分开的,您需要指定-lm
才能链接它。这在很大程度上是因为硬件浮点不是通用的,所以有些机器需要一个使用硬件的库,而其他机器需要软件仿真浮点算法。某些平台(例如Linux)如果使用<math.h>
(可能是<tgmath.h>
)中声明的函数,仍然需要单独的-lm
选项;其他平台(例如Mac OS X)不会 - 有一个-lm
来满足构建系统的链接,但数学函数在主C库中。
如果代码是一个相当标准的32位平台ILP32(int
,long
,指针所有32位),在编译然后许多体系,假定strerror()
返回一个int
假定它返回相同量的数据好像它返回char *
(这是strerror()
实际返回的值)。因此,当代码将strerror()
的返回值压入fprintf()
的堆栈时,会推送正确的数据量。请注意,某些架构(特别是摩托罗拉M680x0系列)将返回地址寄存器(A0)中的地址和通用寄存器(D0)中的数字,因此即使在具有32位编译的机器上也会出现问题:编译器会尝试从数据寄存器而不是地址寄存器中获取返回值,并且这不是由strerror()
设置的 - 导致混乱。
随着64位体系结构(LP64),假设strerror()
返回一个32位int
意味着,编译器将仅收集由strerror()
返回的64位地址的32位和推送堆栈上为fprintf()
到与...合作。当它试图将截断地址视为有效时,事情就会出错,经常导致崩溃。
当添加缺少的<string.h>
头,编译器知道该strerror()
函数返回一个char *
和所有的幸福和喜悦再次,即使该文件的程序被告知要寻找不存在。
如果你是明智的,你将确保你的编译器总是在繁琐的模式下编译,拒绝任何可能错误的东西。当我用我的默认的编译您的代码,我得到:
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition bogus.c -o bogus
bogus.c: In function ‘main’:
bogus.c:10:33: error: implicit declaration of function ‘strerror’ [-Werror=implicit-function-declaration]
fprintf(stderr, "%s\n", strerror(errno));
^
bogus.c:10:25: error: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Werror=format=]
fprintf(stderr, "%s\n", strerror(errno));
^
bogus.c:10:25: error: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Werror=format=]
bogus.c:4:14: error: unused parameter ‘argc’ [-Werror=unused-parameter]
int main(int argc, char *argv[])
^
cc1: all warnings being treated as errors
$
的“未使用的参数”的错误提醒你,你应该检查到有传递给fopen()
您尝试打开文件之前的说法。
固定码:
#include <string.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if (argc != 2)
{
fprintf(stderr, "Usage: %s file\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "r");
if (fp == NULL)
{
fprintf(stderr, "%s: file %s could not be opened for reading: %s\n",
argv[0], argv[1], strerror(errno));
return errno;
}
printf("file %s exists\n", argv[1]);
fclose(fp);
return 0;
}
体形:
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition bogus.c -o bogus
$
运行:
$ ./bogus bogus
file bogus exists
$ ./bogus bogus2
./bogus: file bogus2 could not be opened for reading: No such file or directory
$ ./bogus
Usage: ./bogus file
$
注意,错误消息包括节目名称和标准错误报告。当文件已知时,错误消息包含文件名;这是很容易调试一个错误,如果该程序是在shell脚本比如果消息只是:
No such file or directory
没有指出哪些程序或文件时遇到的问题。
当我删除从固定代码#include <string.h>
线所示,那么我可以编译并像这样运行:
$ gcc -o bogus90 bogus.c
bogus.c: In function ‘main’:
bogus.c:18:35: warning: implicit declaration of function ‘strerror’ [-Wimplicit-function-declaration]
argv[0], argv[1], strerror(errno));
^
$ gcc -std=c90 -o bogus90 bogus.c
$ ./bogus90 bogus11
Segmentation fault: 11
$
将其用在Mac OS X 10.10.5 GCC 5.1.0测试 - 当然,这是一个64位平台。
我知道,但如何链接器链接这个字符串库没有我明确提供'#包括'在开始 –
codeomnitrix
我发现这*** [对话](http://stackoverflow.com/a/12879085/ 645128)***可能有助于回答... – ryyker
链接器不关心'#include'指令。链接器链接它被告知链接的内容。 –