我读了几篇帖子并得出结论,extern告诉编译器"这个函数存在,但它的代码在其他地方.不要惊慌." 但链接器如何知道函数的定义位置.
我的案例: - 我正在研究Keil uvision 4.有一个头文件grlib.h,主函数在grlib_demo.c(它包含grlib.h).现在,有一个函数GrCircleDraw()在Circle.c中定义并在grlib_demo.c中调用,还有一个语句
extern void GrCircleDraw(所有参数);
在grlib.h中.我的查询是链接器如何知道GrCircleDraw()的定义,因为Circle.c不包含在grlib.h和grlib_demo.c中
注意: - 文件grlib.h和Circle.c位于同一文件夹中.代码运行成功.
当您以ELF 格式编译 .o 文件时,文件中有很多内容,.o例如:
.text包含代码的部分;.data, .rodata,.rss包含全局变量的部分;.symtab包含.o(及其在文件中的位置)中的符号(函数、全局变量等)列表以及.o文件使用的符号;.rela.text重定位列表之类的部分——这些是链接编辑器(和/或动态链接器)为了将程序的不同部分链接在一起而必须进行的修改。让我们编译一个简单的 C 文件:
extern void GrCircleDraw(int x);
int foo()
{
GrCircleDraw(42);
return 3;
}
int bla()
{
return 2;
}
Run Code Online (Sandbox Code Playgroud)
和:
gcc -o test.o test.c -c
Run Code Online (Sandbox Code Playgroud)
(我使用的是系统的本机编译器,但在交叉编译到 ARM 时它的工作原理完全相同)。
您可以使用以下命令查看 .o 文件的内容:
readelf -a test.o
Run Code Online (Sandbox Code Playgroud)
在符号表中,您会发现:
符号表 '.symtab' 包含 10 个条目:
Num:值大小类型绑定可视 Ndx 名称
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
[...]
8: 0000000000000000 21 FUNC 全局默认值 1 foo
9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GrCircleDraw
10: 0000000000000015 11 FUNC 全局默认值 1 bla
我们的foo函数有一个符号,一个用于bla。值字段给出了它们在该.text部分中的位置。
使用的符号有一个符号GrCircleDraw:它是未定义的,因为此函数未在此.o文件中定义,但仍待在其他地方找到。
在.text( .rela.text)部分的重定位表中,您可以找到:
偏移量 0x260 处的重定位部分“.rela.text”包含 1 个条目: 偏移信息类型符号。价值符号。名称 + 加号 00000000000a 000900000002 R_X86_64_PC32 0000000000000000 GrCircleDraw - 4
此地址在foo: 链接编辑器将使用GrCircleDraw函数地址修补此地址处的指令。
现在让我们自己编译一个实现GrCircleDraw:
void GrCircleDraw(int x)
{
}
Run Code Online (Sandbox Code Playgroud)
让我们看看它的符号表:
符号表 '.symtab' 包含 9 个条目:
Num:值大小类型绑定可视 Ndx 名称
[...]
8: 0000000000000000 9 FUNC 全局默认值 1 GrCircleDraw
它有一个条目用于GrCircleDraw定义其在其.text部分中的位置。
因此,当链接编辑器将两个文件组合在一起时,它知道:
.o文件中定义了哪些函数及其位置;编译器只是将extern函数的名称放入.obj文件中。编译器不需要了解更多。
当您开始链接时,作为开发人员,您有责任将所有必要的目标文件和库文件提供给链接器。链接器会将所有这些函数排列成一个二进制文件。如果您没有指定正确的库或.obj文件,链接将简单地失败并显示unresolved blah-blah.
默认库通常是隐式包含的。这使事情复杂化并产生幻觉。您始终可以指定不想要任何隐式库并显式包含所有内容。不幸的是,每个系统都以自己的方式做到这一点。
| 归档时间: |
|
| 查看次数: |
2363 次 |
| 最近记录: |