第四章 链接器
- 4.1 链接器是什么?
- 4.2 声明与定义
- 4.3 命名冲突
-
- 4.3.1 命名冲突
- 4.3.2 static修饰符
- 4.4 形参、实参、返回值
- 4.5 检查外部类型
- 4.6 头文件
4.1 链接器是什么?
典型的链接器将编译器或汇编器生成的几个目标模块集成到一个被称为载入模块或可执行文件的实体中–操作系统可以直接执行该实体。
链接器通常将目标模块视为一组外部对象。每个外部对象代表机器内存的某部分,并通过外部名称进行识别。因此,==程序中的每个函数和每个外部变量,如果没有被声明为static,都是外部对象。==一些C编译器会改变静态函数和静态变量的名称,并将其作为外部对象。由于名称修改,它们不会与其他源程序文件中的同名函数或同名变量发生冲突。
4.2 声明与定义
extern int a;
int a; extern int a;
以上两个句子可以在同一源文件或程序的不同源文件中。
==注:每个外部变量只能定义一次。==若外部变量的多个定义各指定一个初始值,例如:
int a = 7;
出现在源文件中,
int a = 9;
在另一个源文件中,大多数系统拒绝接收该程序。但是,如果一个外部变量在多个源文件中没有指定初始值,则**有些系统会接受这个程序,而其他系统则不会接受。**因此,每个外部变量必须只定义一次。
4.3 命名冲突
4.3.1 命名冲突
如果定义包含在两个不同的源文件中
int a;
4.3.2 static修饰符
static int a;
static修饰a后,a对于其他源文件,a是不可见的,且无法再被extern引用,当然,static也适用于函数static之后,我们可以在其他源文件中定义这一点static修改后的同名变量或函数。
4.4 形参、实参、返回值
注:如果函数没有float、short、或者char函数声明中可以完全省略参数类型的说明(注意函数定义中不能省略参数类型的说明)。这取决于调用者能够提供数量正确、类型合适的实参。恰当并不意味着等同:float类型参数将自动转换为double类型,short或者char类型参数将自动转换为int类型。
在ANSI C在标准发布之前,通常会有以下声明和定义函数:
int isvowel();///声明函数的方式 int isvowel(c) char c; { return c =='a' ; }
事实上,以上写法等同于以下写法:
int isvowel(int i) { char c; return c=='a'; }
以上两种方法是VS支持2019年。
看下面的例子:
#include<stdio.h> int main() { int i; char c; for (i = 0; i < 5; i ) { scanf("%d", &c); printf("%d ", i); } printf("\n"); return 0; }
该程序从标准输入设备读入5个数,在标准输出设备上写5个数:
0 1 2 3 4
事实上,这个程序并不一定会得到上述结果。例如,在编译器上,它的输出是(当然,在VS由于内存空间的非法修改,程序在2019年环境下会崩溃)
0 0 0 0 0 1 2 3 4
为什么呢?问题的关键在于,这里的c被声明是char类型,而不是int类型。若程序要求scanf读一个整数,应该给他一个指向整数的指针。而程序中scanf函数是指向字符的指针,scanf函数无法区分这种情况。它只接受指向字符的指针作为指向整数的指针,并将整数存储在指针的位置。由于整数占用的存储空间大于字符占用的存储空间,因此字符c附近的内存被覆盖。
存储在字符c附近的内存由编译器决定,存储在整数i的低端部分。因此,每次读取数值到c时,i的低端部分将被覆盖为0,而i的高端部分最初是0,相当于i每次被重置为0,循环将继续进行。到达文件结束时,scanf函数不再试图读入新的值c。这时,i正常运行,最后终止循环。
4.5 检查外部类型
注:确保每个目标模块中具有相同类型的所有特定类型的外部定义,相同类型也应严格相同。
例如,在文件中包含定义:
char filename[] = "/etc/passwd";
另一份文件包含声明:
extern char *filename;
在定义时,filename是字符数组的名称。虽然在句子中引用filename指向数组起始元素的指针是值,但是filename类型是字符数组,而不是字符指针。在第二份声明中,filename被确定为指针。这两种方式使用不同的存储空间,它们不能以合理的方式共存。第一个例子字符数组filename如下图所示:
字符指针的第二种方法filename如下图所示:
修改方法如下图所示:
char filename[] = "/etc/passwd"; extern char filename[];
也可以这样修改:
char*filename = "/etc/passwd"; extern char *filename;
4.6 头文件
注:每个外部对象只在一个地方声明,该声明通常在第一个文件中,所有需要使用外部对象的模块也应该 包括在这个头文件。特别指出的是,定义外部对象的模块也应包括头文件。