2015-09-25 57 views
1

我刚刚遇到这个代码,博客表示这在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

我知道,但如何链接器链接这个字符串库没有我明确提供'#包括'在开始 – codeomnitrix

+1

我发现这*** [对话](http://stackoverflow.com/a/12879085/ 645128)***可能有助于回答... – ryyker

+0

链接器不关心'#include'指令。链接器链接它被告知链接的内容。 –

回答

5

显示,如果你让编译器推断这并不总是声明函数返回一个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(intlong,指针所有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位平台。

1

我不认为这个代码的功能会受到其32位或64位架构的影响:指针是32位还是64位,如果long int是32位或64位。包含头文件(在本例中为string.h)不应该影响与库的链接。头文件包含对编译器而不是链接器。编译器可能会警告隐式声明的函数,但只要链接器可以在其中一个正在搜索的库中找到该函数,它就会成功链接该二进制文件,并且它应该运行得很好。

我刚刚在64位CentOS盒子上使用叮当声3.6.2创建并运行了该代码。我没有得到这个编译器警告:

junk.c:10:33: warning: implicitly declaring library function 'strerror' with type 'char *(int)' 
     fprintf(stderr, "%s\n", strerror(errno)); 
           ^
junk.c:10:33: note: include the header <string.h> or explicitly provide a declaration for 'strerror' 
1 warning generated. 

该节目进行了一个不存在的文件名和错误消息,“没有这样的文件或目录”,是有意义的。然而,这是因为strerror()函数是一个众所周知的标准库函数,并且它的声明被编译器正确地猜到了。如果它是用户定义的函数,那么编译器在猜测时可能不那么“幸运”,然后架构可能很重要,正如其他答案所建议的那样。

因此,吸取的教训:确保函数声明可用于编译器并注意警告!

+0

什么是编译器版本?你用来编译的命令行是什么?您是否使用命令行上的文件运行程序?该文件是否存在?你有没有得到任何编译器警告? –

+3

它受到影响,因为'strerror'返回'char *'。不包括头文件,编译器不知道这个,并假定它返回一个'int'。这发生在32位平台上,因为它们的大小相同。但是在指针是整数大小的两倍的64位平台上它会发生可怕的错误。 –

+0

请注意,编译器(clang)使用其标准C的知识来推断函数的正确类型,因此尽管缺少声明,程序仍能正常工作。相比之下,当我使用GCC时,它没有使用它的知识来进行类型推断,所以我崩溃了。有趣。确保代码编译没有警告仍然好得多 - 当然在默认模式下,最好甚至当编译器设置得相当模糊时。 ('clang -Weverything'虽然很难满足,但是!) –