栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c语言-编译与链接

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c语言-编译与链接

从源代码生成可执行文件可以分为四个步骤:分别是预处理、编译、汇编和链接。

一、预处理

处理源文件和头文件中以#开头的命令,比如 #include、#define、#ifdef 等。预处理的规则一般如下:
1、将所有的#define删除,并展开所有的宏定义。
2、处理所有条件编译命令,比如 #if、#ifdef、#elif、#else、#endif 等。
3、处理#include命令,将被包含文件的内容插入到该命令所在的位置,递归进行。
4、删除所有的注释。
5、添加行号和文件名标识。
6、保留所有的#pragma命令,因为编译器需要使用它们。

预处理的结果是生成.i文件,gcc下使用下面命令生成i文件:

gcc -E demo.c -o demo.i    //  -E表示只进行预编译
二、编译

编译就是把预处理完的文件进行一些列的词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件,由.i文件生成.s文件。最难的一部分,有专门的书籍深入研究,不细说。

三、汇编

汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令,一般是根据汇编语句和机器指令的对照表一一翻译。

四、链接

A.链接的作用:经汇编过后的.o目标文件已经是二进制文件,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。
B.举例:假设一个程序有两个模块 main.c 和 module.c,在 module.c 中定义了函数 func(),并在 main.c 中进行了调用,但由于每个模块都是单独编译的,编译器在处理 main.c 时并不知道 func() 的地址,所以需要把这些调用 func() 的指令的目标地址搁置,等到最后链接的时候再由链接器将这些地址修正。
C.符号与符号决议(重定位):符号表示一个函数或变量的地址,符号决议表示计算符号地址的过程。
D.符号决议计算地址的过程:要进行链接时,链接器首先扫描所有的.o目标文件,获得各个段的长度、属性、位置等信息,并将目标文件中的所有(符号表中的)符号收集起来,统一放到一个全局符号表。在这一步中,链接器会将目标文件中的各个段合并到可执行文件,并计算出合并后的各个段的长度、位置、虚拟地址等。在目标文件的符号表中,保存了各个符号在段内的偏移,生成可执行文件后,原来各个段起始位置的虚拟地址就确定了下来,这样,使用起始地址加上偏移量就能够得到符号的地址(在进程中的虚拟地址)。
E.强符号和弱符号:在多个源文件中定义了名字相同的全局变量会造成符号重复定义,而在这些重复定义的符号中,函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。链接器对于强弱符号的规则如下:
1、 不同的目标文件中不能有同名的强符号。
2、 一个符号在某个目标文件中是强符号,在其他文件中是弱符号,那么选择强符号。
3、 一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。
在 GCC 中,可以通过__attribute__((weak))来强制定义任何一个符号为弱符号:

int weak1;           //弱符号
int strong = 100;      //强符号
__attribute__((weak)) weak2 = 2;  //弱符号

注:对于同一个变量或函数,强弱符号只能在不同的目标文件中定义,在同一目标文件中同时存在强弱符号会报错。

F.强引用和弱引用:对强符号的引用称为强引用,对弱符号的引用称为弱引用。强引用要求符号必须被定义,否则链接器报错,弱引用符号未定义时,链接器不报错,例如:

#include 
__attribute__((weak)) extern int a;
__attribute__((weak)) extern void func();  
int main(){
    printf("&a: %d, func: %dn", &a, func); //地址为0
    printf("a = %dn", a);  //弱引用,变量未定义链接器不报错,但0地址不可访问,运行报段错误
    func();   //弱引用,函数未定义链接器不报错,但0地址不可访问,运行报段错误
    return 0;
}

弱引用和强引用非常利于程序的模块化开发,可以将程序的扩展模块定义为弱引用,当我们将扩展模块和程序链接在一起时,程序就可以正常使用;如果我们去掉了某些模块,那么程序也可以正常链接,只是缺少了某些功能。

五、extern和static关键字

A.extern关键字
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。例如:
main.c中:

extern void func();  //函数和全局变量在其他文件中定义,提示在其他文件中寻找
extern int m;  

注:exterb仅用于声明,所以不推荐这种写法:

extern datatype name = value;  //相当于datatype name = value,extern没起作用

B.static关键字

static修饰全局变量或函数时,会修改其作用域,static修饰的全局变量或函数仅对本文件有效。
static修饰局部变量,会改变变量的存储区域,被 static 修饰的局部变量会存储在全局数据区(与全局变量存储的区域一样),而未被static修饰的局部变量应该是在栈空间里的。所以不会因为函数调用结束而销毁,同时注意全局数据区的变量只能被初始化(定义)一次。

下面这个例子:

#include 
int func(){
    static int n = 0;           //static修饰局部变量
    n++;
    printf("Function is called %d times.n", n);
    return n;
}
int main(){
    int i, n = 0;
    for(i = 1; i<=5; i++){
        func();
    }
    printf("n = %dn", n);
    return 0;
}

运行结果:
Function is called 1 times.
Function is called 2 times.
Function is called 3 times.
Function is called 4 times.
Function is called 5 times.
n = 0

注意static int n 只初始化一次,且static int n 和main里的n互不影响(main的n在栈空间里)

更多动静态库,共享库的创建、链接问题见linux系统编程-静态库、共享库的创建和链接。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/433549.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号