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

How are library functions are linked in this case?

我刚看到这段代码,博客上说这在 32 位架构上运行良好。我没有测试它;但是,在这种情况下,我对图书馆的link年龄有疑问。编译器如何将 link 字符串库转换为 main,因为它不知道要转换哪个库 link?

所以基本上如果我包含 <string.h> 那么它应该可以正常工作;但是,如果我不包含 <string.h>,那么根据博客,它在 32 位架构中 运行s 而在 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;
}

仅当您允许编译器推断未声明的函数总是 return 和 int 时,显示的代码才会编译。这在 C89/C90 中有效,但已过时; C99 和 C11 要求函数在使用前声明。 GCC 5.1.0 之前的版本默认采用 C90 模式;你必须打开 'reject this code' 警告。 GCC 5.1.0 及更高版本默认采用 C11。你至少会从代码中得到警告,即使没有任何编译选项来打开它们。

代码将 link 正常,因为函数名称是 strerror() 无论是否声明,并且 linker 可以在标准 C 库中找到该函数.通常,标准 C 库中的所有函数都自动可供 linking 使用——而且,实际上,通常还有很多不那么标准的函数也可用。 C 不像 C++ 那样有 type-safe linkage(但 C++ 也坚持在使用前声明每个函数,因此如果没有 header,代码将无法编译为 C++。)

由于历史原因,数学库是独立的,您需要指定 -lm 才能 link。这在很大程度上是因为硬件浮点不是通用的,所以一些机器需要使用硬件的库,而其他机器需要浮点算法的软件模拟。如果您使用在 <math.h> 中声明的函数(可能还有 <tgmath.h>),某些平台(例如 Linux)仍然需要一个单独的 -lm 选项;其他平台(Mac OS X,例如)没有——有一个 -lm 来满足 link 它的构建系统,但是数学函数在主 C 中图书馆。

如果代码是在相当标准的 32 位平台上用 ILP32 编译的(intlong,指针都是 32 位),那么对于许多架构,假设 strerror() returns an int 假定它 returns 与 returns a char * 相同的数据量(这就是 strerror() 实际上 returns)。因此,当代码将 strerror() 中的 return 值推入堆栈 fprintf() 时,正确数量的数据被推入。

请注意,某些体系结构(尤其是摩托罗拉 M680x0 系列)会 return 地址寄存器 (A0) 中的地址和通用寄存器 (D0) 中的数字,因此即使在那些具有32 位编译:编译器会尝试从数据寄存器而不是地址寄存器获取 returned 值,而该值不是由 strerror() 设置的——导致混乱。

对于 64 位架构 (LP64),假设 strerror() returns 是 32 位的 int 意味着编译器只会收集 64 位的 32 位地址 return 由 strerror() 编辑并将其压入堆栈以供 fprintf() 使用。当它试图将 t运行cated 地址视为有效时,事情会出错,通常会导致崩溃。

当缺少的<string.h> header被添加时,编译器知道strerror()函数return是一个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
$

'unused argument' 错误提醒您在尝试打开文件之前应检查是否有参数要传递给 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 位平台。

我认为此代码的功能不会受到其 32 位或 64 位体系结构的影响:指针是 32 位还是 64 位并不重要,long int 是32 位或 64 位。包含 headers,在本例中为 string.h,也不应影响 linking 到图书馆。 Header 包含对编译器很重要,而不是 linker。编译器可能会警告函数被隐式声明,但只要 linker 可以在它搜索的库之一中找到该函数,它就会成功 link 二进制文件,并且它应该运行就好了。

我刚刚使用 clang 3.6.2 在 64 位 CentOS 机器上成功构建并 运行 这段代码。我确实收到了这个编译器警告:

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.

该程序被赋予了一个 non-existent 文件名,错误消息 "No such file or directory," 是有意义的。然而,这是因为 strerror() 函数是一个 well-known 标准库函数,它的声明被编译器正确猜到了。如果它是一个 user-defined 函数,编译器可能不会 "lucky" 猜测,然后架构可能很重要,正如其他答案所建议的那样。

所以,吸取教训:确保函数声明对编译器可用并注意警告!

我解决了 strings.h header

#include <string.h>