【C 陷阱与缺陷】(四)连接

本文介绍了C语言中的链接过程,包括连接器的工作原理、声明与定义、命名冲突与`static`修饰符的使用、形参、实参与返回值的处理,以及头文件的管理。强调了外部对象的定义和类型一致性的重要性,通过实例解析了可能出现的问题及解决方案。
摘要由CSDN通过智能技术生成

码字不易,对你有帮助 点赞/转发/关注 支持一下作者

微信搜公众号:不会编程的程序圆

看更多干货,获取第一时间更新

代码,练习上传至:

https://github.com/hairrrrr/C-CrashCourse

一 链接

0. 什么是连接器

C 语言的一个重要思想就是分别编译(separate compilation),即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合在一起。但是,连接器一般是与 C 编译器分离的,它不可能了解 C 语言的诸多细节。

**连接器的工作原理:**连接器的输入是一组目标模块和库文件。连接器的输出是一个载入摸块。连接器读入目标模块和库文件同时生皮载入模块。对每个目标核块中的每个外部对象,链接器都要检查载入模块。查看是否有同名的外部对象。如果没有,连接器就将该外部对象添加到入模块中,如果有,连接器就要开始处理命名冲突。

**外部对象:**程序中的每个函数和每个外部变量,如果没有被声明为 static,就都是一个外部对象。

除了外部对象之外,目标模块中还可能包括了对其他模块中的外部对象的引用。例如一个调用了函数 printf 的 C 程序所生成的目标模块,就包括了一个对库函数 printf 的引用。可以推测得出,该引用指向的是一个位于某个库文件中的外部对象。在连接器生成载入模块的过程中,它必须同时记录这些外部对象的应用。当连接器读入一个目标模块时,它必须解析出这个目标模块中定义的所有外部对象的引用,并标记这些外部对象不再是未定义的。

1. 声明与定义

声明语句:

int a;

如果其位置出现在所有函数体之外,那么它就被称为外部对象 a 的定义。这个语句说明了 a 是一个外部整型变量,同时为 a 分配内存空间。它的初始值默认为 0 。

下面的声明语句:

int a = 7;

不仅为 a 分配了内存空间,而且说明了在该内存中应该存储的值。

下面的声明语句:

extern int a;

并不是对 a 的定义。这个语句仍然说明了 a 是一个外部整型变量,但是 a 的存储空间是在程序的其他地方分配的。从连接器的角度来看,上面的声明是对 a 的引用,而不是定义。

void srand(int n){
   
    extern int random_seed;
    random_seed = n;
}

每个外部对象都必须在某个地方进行定义。因此,如果程序中包括了语句:

extern int a;

那么,这个程序就必须在别的某个地方包括语句:

int a;

这两个语句既可以是在同一个源文件中,也可以位于程序的不同源文件中。

严格的规则是,每个外部变量都只能被定义一次

2. 命名冲突与 static 修饰符

两个具有相同名称的外部对象实际上代表的是同一个对象,即使编程者的本意并非如此,但系统却会如此处理。因此,如果在两个不同的源文件中都包括了定义:

int a;

那么,它或者表示程序错误(如果连接器禁止外部变量重复定义的话),或者在两个源文件中共享 a 的同一个实例(无论两个源文件中的外部变量 a 是否应该共享)。

即使其中 a 的一个定义是出现在系统提供的库文件中,也仍然进行同样的处理。当然,一个设计良好的函数库不至于定义 a 作外部名称。但是,要了解函数库中定义的所有外部对象名称却也并非易事。类似于read 和 write 这样的名称不难猜到,但其他的名称就没有这么容易了。

static 修饰符是一个能够减少此类命名冲突的有用工具。例如,以下声明语句:

static int a;

其含义与下面的语句相同

int a;

只不过,a 的作用域限制在一个源文件内,对于其他源文件,a是不可见的。因此,如果若干个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一一个源文件中以 static 修饰符声明。

static修饰符不仅适用于变量,也适用于函数。如果函数 f 需要调用另一个函数 g ,而且只有函数 f 需要调用函数 g ,我们可以把函数 g 和 f 放到同一个源文件中,并声明函数 g 为 static:

static int g(int x){
   
    // 函数体
}
int f(){
   
    // 其他内容
    b = g(a);
}

我们可以在多个源文件中定义同名的函数 g,只要所有的函数 g 都被定义为 static,或者仅仅只有其中一个函数 g 不是static 。因此,为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,我们就应该声明该函数为 static。

3. 形参,实参与返回值

如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义,那么就不会有任何与返回类型相关的麻烦。

比如一个调用 square 函数的程序:

main(){
   
    printf(
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值