我刚刚看到这个代码,博客说这在32位架构上运行良好。我没有测试它;但是,我怀疑在这种情况下图书馆的联系。编译器如何将字符串库链接到main
,因为它不知道要链接哪个库?
所以基本上如果我包含<string.h>
那么它应该可以正常工作;但是,如果我不包括<string.h>
那么,根据博客,它运行在32位架构中,无法在64位架构上运行。
#include <errno.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "%s\n", strerror(errno));
return errno;
}
printf("file exist\n");
fclose(fp);
return 0;
}
答案 0 :(得分:5)
如果您允许编译器推断未声明的函数始终返回int
,则显示的代码将仅编译。这在C89 / C90中有效,但已标记为过时; C99和C11要求在使用之前声明函数。版本5.1.0之前的GCC默认采用C90模式;你不得不拒绝这个代码&#39;警告。 GCC 5.1.0及以后版本默认采用C11。即使没有任何编译选项,您也至少会从代码中收到警告,以便打开它们。
代码将链接正常,因为函数名称是strerror()
,无论是否声明它,链接器都可以在标准C库中找到该函数。通常,标准C库中的所有函数都自动可用于链接 - 实际上,通常还有很多不太标准的函数可用。 C没有像C ++那样的类型安全链接(但是C ++也坚持在使用之前声明每个函数,因此代码不会在没有标题的情况下编译为C ++。)
由于历史原因,数学库是独立的,您需要指定-lm
才能链接它。这在很大程度上是因为硬件浮点不是通用的,因此一些机器需要使用硬件的库,而其他机器需要浮点运算的软件仿真。如果使用-lm
中声明的函数(可能是<math.h>
),某些平台(例如Linux)仍然需要单独的<tgmath.h>
选项;其他平台(例如Mac OS X)没有 - 有-lm
来满足链接它的构建系统,但数学函数在主C库中。
如果代码是在相当标准的32位平台上编译的,其中ILP32(int
,long
,指针都是32位),那么对于许多架构,假设strerror()
返回int
假设它返回相同数量的数据,就像它返回char *
(strerror()
实际返回的那样)。因此,当代码将返回值从strerror()
推送到fprintf()
的堆栈时,会推送正确的数据量。
请注意,某些体系结构(特别是Motorola M680x0系列)会返回地址寄存器(A0)中的地址和通用寄存器(D0)中的数字,因此即使在具有32位编译的计算机上也会出现问题:编译器会尝试从数据寄存器而不是地址寄存器中获取返回值,而这不是由strerror()
设置的 - 导致混乱。
对于64位架构(LP64),假设strerror()
返回32位int
意味着编译器将仅收集由{{1}返回的64位地址的32位并在堆栈上推送strerror()
来使用。当它试图将截断的地址视为有效时,事情就会出错,通常会导致崩溃。
当添加缺少的fprintf()
标头时,编译器知道<string.h>
函数返回strerror()
并且一切都是幸福和再次高兴,即使程序被告知文件寻找不存在。
如果你是明智的,你将确保你的编译器总是在繁琐的模式下编译,拒绝任何可能错误的东西。当我在你的代码上使用我的默认编译时,我得到:
char *
&#39;未使用的参数&#39;错误提醒您在尝试打开文件之前应检查是否有参数传递给$ 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
$
请注意,错误消息包括程序名称和报告标准错误。当文件已知时,错误消息包括文件名;如果程序在shell脚本中而不是消息只是:
,则调试该错误要容易得多$ ./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
$
没有指出哪个程序或哪个文件遇到问题。
当我从显示的固定代码中删除No such file or directory
行时,我可以编译它并像这样运行它:
#include <string.h>
这是在Mac OS X 10.10.5上使用GCC 5.1.0进行测试的 - 当然,这是一个64位平台。
答案 1 :(得分:1)
我不认为这个代码的功能会受到其32位还是64位架构的影响:指针是32位还是64位并不重要,如果长则不行int是32位或64位。包含头文件(在本例中为string.h)也不应影响到库的链接。头包含对编译器很重要,而不是链接器。编译器可能会警告隐式声明的函数,但只要链接器可以在其中一个库中找到该函数,它就会成功链接二进制文件,它应该运行得很好。
我刚刚使用clang 3.6.2在64位CentOS盒子上成功构建并运行了此代码。我确实得到了这个编译器警告:
tg <- textGrob("Cars MPG per CARB", gp=gpar(fontsize=16,font=1))
grid.arrange(tg, tab, heights=unit.c(grobHeight(tg), sum(tab$heights)),
vp=viewport(x=unit(0,"npc") +
0.5*unit.pmax(grobWidth(tg),
sum(tab$widths))))
该程序被赋予了一个不存在的文件名和错误消息,&#34;没有这样的文件或目录,&#34;很有意义。但是,这是因为strerror()函数是一个众所周知的标准库函数,并且编译器正确地猜测了它的声明。如果它是用户定义的函数,编译器可能不是这样的&#34;幸运&#34;在猜测,然后建筑可以重要,如其他答案所示。
所以,吸取了教训:确保编译器可以使用函数声明并注意警告!
答案 2 :(得分:1)
我通过添加strings.h
标头来解决
#include <string.h>