打印
[菜农群课笔记]

20120410群课笔记-c链接器续

[复制链接]
1289|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zxs2000|  楼主 | 2012-4-11 13:58 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 zxs2000 于 2012-4-11 14:27 编辑

由于昨晚听课是用手机,所以以下内容是根据微群http://q.weibo.com/316539整理的:


我们今天继续讨论链接器相关的知识

John Lee:
下面的语句: int bar; 在声明对象的同时,也“定义”了这个对象。我们就简单地说“定义(变量)”就行了
John Lee:
如果这个定义的位置在任何函数之外,那么它就定义了一个“外部对象”,对象的名称是 bar,类型是 int。
John Lee:
既然是“定义”,那么就要为这个对象分配存储空间。分配的存储空间,需要有两个基本属性才能确定: 1、起始地址 2、长度
John Lee:
这样做理由很简单:编译器被设计为可以分别编译多个源程序的,那么它就不知道最终的目标(target)系统中,到底有多少个外部对象。
John Lee:
而链接器则是被设计来将全部的目标文件(.o)以及库文件,整合输出到最终的执行文件,它自然知道构成执行文件所需的全部对象信息,那么为各个对象安排起始地址,便是很容易的事。
John Lee:
我们继续讨论外部对象,刚才说到了声明并且同时定义外部对象。
John Lee:
那么,下面的语句: extern int bar;
John Lee:
这个语句也是声明对象,但并不定义它,按语法,编译器将不会为这个对象分配空间,但程序中,访问该对象的表达式,编译器会予以承认(假设语**确的话),并且编译器会在其生成的.o文件中,说明一个未定义的符号(就是对象的名称),但没有其它相关信息,如长度、类型等等。
John Lee:
如果在所有的源程序中都没有“定义”这个对象 bar,那么链接器将会报错:“未定义的符号 bar”。
John Lee:
实际上就是要求我们在源程序中,必须定义这个外部对象,以保证通过链接。
John Lee:
假设我们定义了 bar,通过了链接,就能保证程序逻辑的正确性吗?
John Lee:
刚才说了,编译器在.o文件中说明一个未定义的符号时,没有说明长度、类型等相关信息。
John Lee:
我们来做一个实验: // a.c 文件 #include <stdio.h> extern int bar; int main(void) { printf("%d\n", bar); } ------------- // b.c 文件 char bar = -1;
John Lee:
定义了为啥 没有长度,类型的信息呢 -------------- 这个问题,要考古了。
John Lee:
目标文件格式中,就没有相关的信息。
John Lee:
上面的程序,编译链接都应该都完全通过。
John Lee:
但程序显然有很严重的错误(隐患)。
John Lee:
甚至把 b.c 文件写成这样: void bar(void) { } 也可以通过编译链接。
John Lee:
对付这种问题的正确方法是,在头文件中声明外部对象(函数和变量等),在某个.c文件中,定义这个外部对象:
John Lee:
// c.h 文件 extern int bar; // 声明全局变量 bar --------------- // a.c 文件 #include "c.h" int main(void) { printf("%d\n", bar); }
John Lee:
// b.c 文件 #include "c.h" int bar; // 定义全局变量 bar,也可以初始化, // 由于在 c.h 中已经声明了 bar,所以此处的“定义”必须完全符合声明。
John Lee:
就是为了预防声明和定义的不一致
John Lee:
某些同学可能有一个小疑问,b.c 经过编译预处理展开后,应该是这样的: extern int bar; int bar; 这样合法吗?完全合法。
John Lee:
如果同一外部对象,在多个源程序或者头文件中被定义(注意,相当多的菜鸟在头文件中定义变量),链接器又将如何处理呢?
John Lee:
例如: // c.h int bar; int foo(void); ----------- // a.c #include "c.h" int main(void) { printf("%d\n", foo() + bar); }
John Lee:
-------------- // b.c #include "c.h" int foo(void) { return bar; }
John Lee:
我们分析一下,编译器在编译 a.c 时,在生成的 a.o 里,说明了一个外部对象 bar。在编译 b.c 时,在生成的 b.o 里,也说明了一个外部对象 bar。
John Lee:
当链接时,链接器发现需要链接的两个文件(a.o 和 b.o)中,都存在外部对象 bar 的定义。
John Lee:
两个.o文件里的 bar, 都是由 c.h 引来的
John Lee:
大多数链接器会报错:“多次定义符号 bar”。
John Lee:
我们把a.c展开,得到: int bar; int foo(void); int main(void) { printf("%d\n", foo() + bar); }
John Lee:
然后把b.c展开,得到: int bar; int foo(void); int foo(void) { return bar; }
John Lee:
好,继续,刚才说到,大多数链接器会报错:“多次定义符号 bar”。
John Lee:
但有一个规则,大家要记住,库文件中的.o,如果没有没引用到的话,即使其中有重复定义的符号,也不会报错。
??
John Lee:
typedef 的,都属于“类型定义”,而不属于对象定义。
John Lee:
需要分配存储空间的,必须是对象(变量,函数等)。
John Lee:
无用的 typedef, 除了降低编译速度外,没有任何副作用
John Lee:
如果你使用了 typedef 所定义的(新)类型,来定义对象(变量)的话,那么就会分配存储空间了。
John Lee:
例如: #include <string.h> size_t strlen(const char* str) { ..... } int main() { printf("%d\n", strlen("hello, world.\n"); } strlen()是标准库函数,但并不妨碍我们自己实现它
John Lee:
链接标准库时,尽管库里已经有了strlen()函数的定义,但并不会造成符号重复定义的错误。
John Lee:
.o 文件,是有优先级的,库中的.o,比源文件(工程中的)生成的.o,优先级要低一些。
John Lee:
源程序的.o,是“必须”链接的,而库中的.o,则是“需要”才链接。
John Lee:
而且,如果库中.o的符号,与源程序.o的符号冲突,那么链接器会优先使用源程序.o中的符号。
John Lee:
问题: // a.c 工程源文件 int bar = 1; int foo(void); int main(void) { printf("%d\n", foo() + bar); } ---------------- // b.c 这个文件编译作为库。 int baz = 2; int foo(void) { return baz; } int bar; int qux(void) { return bar; } 编译链接会出问题吗?要说明原因。
John Lee:
如果某个对象与其它对象,都在同一个段中,那么当此对象被链接时,其它对象也会(必须)被链接的。
John Lee:
a.
c调用了foo() 那么foo()必须要被链接,链接器在b.c的库文件中找到了foo()函数的“定义”。
John Lee:
然后foo()函数访问了baz变量。
John Lee:
导致baz变量也必须被链接
John Lee:
而bar,则与baz处于同一个数据段中
John Lee:
导致b.c的bar也被迫链接了
John Lee:
而链接器在a.c中早已发现了bar定义,现在b.c又出现了bar定义,于是。。。。。。
John Lee:
教大家这些知识,是为了大家(水平上了台阶)以后,做自己的库文件时,需要注意的地方。

20120410群课笔记.rar

21.56 KB

相关帖子

沙发
zxs2000|  楼主 | 2012-4-11 13:59 | 只看该作者
今天李老师补充内容:

John Lee<j.y.lee@yeah.net> 11:22:56
嗯,昨天群课的那个问题:
// a.c 工程源文件
int bar = 1;
int foo(void);
int main(void)
{
    printf("%d\n", foo() + bar);
}
----------------
// b.c 这个文件编译作为库。
int baz = 2;
int bar;
int foo(void)
{
    return baz;
}
今天又想了一下,总感觉有些地方还不是很确定,于是,花了一点时间,仔细做了实验
John Lee<j.y.lee@yeah.net>  11:31:05
结果是这样的:
1、如果库中的.o,没有任何符号(外部对象)被使用,那么,源程序.c中可以存在同名的外部对象符号,链接器优先使用.c中的外部对象。
2、当库中的.o中的任何外部对象符号(函数,数据)被使用,那么该.o中的其它符号也将被链接,不管符号处于何段。

John Lee<j.y.lee@yeah.net>  11:32:41
也就是说,我们如果将b.c改成:
int bar;
int foo(void)
{
    return 1;
}

也一样会链接错误。


John Lee<j.y.lee@yeah.net>  11:34:31
因为foo()函数被使用了,那么,该.o(库中)的bar对象,也将被链接。

John Lee<j.y.lee@yeah.net>  11:35:01
即使foo()函数根本访问bar对象。

John Lee<j.y.lee@yeah.net>  11:35:17
即使foo()函数根本没有访问bar对象。
这个是真正的“株连九族”啊。
John Lee<j.y.lee@yeah.net> 11:37:38
然而,还有一点,比较特殊
John Lee<j.y.lee@yeah.net>  11:38:38
有的目标文件格式,支持一种称为COMMON的段。
这个段专门存放“未初始化”数据对象。

John Lee<j.y.lee@yeah.net>  11:40:05
像:
int bar;
int baz;
这样的,没有赋初值的变量,就会被存放在COMMON段中。

John Lee<j.y.lee@yeah.net>  11:40:50
COMMON段中的各个对象,都是不牵连的。

John Lee<j.y.lee@yeah.net>  11:42:33
如果b.c中的int bar; 被安排到了COMMON段,那么就不会发生链接错误了。

John Lee<j.y.lee@yeah.net>  11:43:43
链接器会使用a.c的int bar,忽略掉b.c中在COMMON段的bar。

使用特权

评论回复
板凳
weshiluwei6| | 2012-4-11 14:11 | 只看该作者
李老师讲课内容啊

使用特权

评论回复
地板
dirtwillfly| | 2012-4-11 14:27 | 只看该作者
顶一下

使用特权

评论回复
5
hongdan0714jin| | 2012-5-8 10:42 | 只看该作者
群号是多少啊???

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

206

帖子

0

粉丝