在这种情况下,库函数是如何链接的?
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 编译的(int
,long
,指针都是 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>
我刚看到这段代码,博客上说这在 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 编译的(int
,long
,指针都是 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>