在这种情况下,库函数如何链接?

时间:2015-09-25 23:44:23

标签: c linker

我刚刚看到这个代码,博客说这在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;
}

3 个答案:

答案 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(intlong,指针都是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>
相关问题