C++后台开发之编译与链接2017/5/12

来源:互联网 发布:拓扑康102n 导入数据 编辑:程序博客网 时间:2024/05/20 01:39

一. 编译与链接

#include <iostream>int main(){    std::cout<<"Hello world\n";    return 0;}

每一位初学者接触所有语言时,都会面对这一行代码,那么它是如何工作的呢。在linux中我们使用g++ -o hello hello.cpp来得到可执行文件,这个过程实际上可以拆分为几个部分。

1。 预处理

首先执行g++ -E hello.cpp -o hello.i,其中-E选项表示只执行到预编译阶段就停止了,预处理阶段主要处理以”#”开头的预编译命令,例如#define,#include 等,当然也包括条件编译。预编译文件.i其实也是ASICC码文件,hello.i如下。

namespace std __attribute__ ((__visibility__ ("default"))){# 60 "/usr/include/c++/4.8/iostream" 3  extern istream cin;  extern ostream cout;  extern ostream cerr;  extern ostream clog;  extern wistream wcin;  extern wostream wcout;  extern wostream wcerr;  extern wostream wclog;  static ios_base::Init __ioinit;}# 2 "hello.cpp" 2using namespace std;int main(){ std::cout<<"Hello wolrd\n"; return 0;}

2。编译与链接

编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件,这个过程往往是整个程序构建的核心部分,也是最复杂的部分之一。上面的编译过程相当于如下命令:

g++ -S hello.i -o hello.s

得到hello.s汇编代码如下:

    .file   "hello.cpp"    .local  _ZStL8__ioinit    .comm   _ZStL8__ioinit,1,1    .section    .rodata.LC0:    .string "Hello wolrd\n"    .text    .globl  main    .type   main, @functionmain:.LFB971:    .cfi_startproc    pushl   %ebp    .cfi_def_cfa_offset 8    .cfi_offset 5, -8    movl    %esp, %ebp    .cfi_def_cfa_register 5    andl    $-16, %esp    subl    $16, %esp    movl    $.LC0, 4(%esp)    movl    $_ZSt4cout, (%esp)    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

使用as汇编得到目标代码hello.o

as -o hello.o hello.s

然后再使用g++链接得到可执行文件。

g++ -o hello hello.o

使用vim hello.o 命令可以知道我hello.o文件已经是elf文件(机器码)了。

^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^A^@^C^@^A^@^@^@^@^@^@^@^@^@^@^^A^@^@^@^@^@^@5^@^@^@^@^@(^@^O^@^L^@U<89>å<83>äð<83>ì^PÇD$^D^@^@^@^^D$^@^@^@^üÿÿÿ¸^@^@^@^ÃU<89>å<83>ì^X<83>}^H^Au1<81>}^Lÿÿ^@^@u(Ç^D$^@^@^@^üÿÿÿÇD$^H^@^@^@^D$^D^@^@^@^^D$^@^@^@^üÿÿÿÉÃU<89>å<83>ì^XÇD$^Dÿÿ^@^^D$^A^@^@^§ÿÿÿÉÃHello wolrd^@c^@^@^@^@GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) ... ...

接下来我们就可以运行./hello得到期待的效果了

root@ubuntu:/home/f411/Desktop/cplustest# ./hello Hello wolrd

3。链接

我个人觉得,链接的工作是所有环节中最有意思的,能够帮助我们很好的理解c语言的体系架构,例如声明与定义的真正区别。第2节的命令g++ -o hello hello.o实际上完成了链接的工作,但是由于只有1个可重定向目标文件,导致过程不是很清晰。

每一个可重定向目标文件*.o都有一个符号表,它包含所定义和引用的符号信息。在链接的上下文中,有三种不同的符号:

(1)由*.o定义并能被其他模块引用的全局符号。全局符号链接器对应于非静态的c函数,以及被定义为不带c static属性的全局变量(强符号)。

(2)由其他模块定义,并且被*.o模块引用的全局符号。这些符号称为外部符号,对应于定义在其他模块中的c函数和变量。(弱符号)

(3)仅能被本地模块定义和引用的本地符号。

在链接中有一个过程称为符号解析,符号解析就是将每个引用与它输入的可重定向目标文件的符号表中的一个确定的符号关联起来,简单的讲就是符号的定位过程。对于简单的局部变量本地符号来说,符号解析非常简单,因为编译器只允许每个模块中的每个本地符号只有一个定义,但是对于全局变量来说就棘手很多了。

在全局符号的解析过程中,遇到一个不是在当前模块中定义的符号,会先假设这个符号是其他模块定义的,并且声称一个链接器符号表条目,把它交给链接器处理。如果链接器在任何输入模块中都找不到该符号,则会输出一条错误信息并终止。

链接器如何解析多重定义全局符号呢:
(1)不允许有多个同名强符号
(2)如果有一个强符号和多个弱符号,那么选择强符号。
(3)如果多个弱符号,则任意选取一个符号。

现有2个c文件:

//foo1.cint main(){    return 0;}//foo2.cint main(){    return 0;}

我们先看两个文件中的符号表:foo1.c中有强符号main,而foo2.c中也有强符号main,两个强符号显然是不行的。

//foo3.c#include <stdio.h>void f(void);  //弱符号int x = 15213; //强符号int main(){    f();    printf("x=%d\n",x);  //输出结果x=15212;    return 0;}//foo4.cint x;void f(){    x=15212;}

以上代码就是遵守一强多弱的规则了,到此可以结合定义与声明来说了,定义就是强符号的定义,而声明则是弱符号的声明。如果一个文件中没有声明,就不能引用另外一个文件中的定义,真正的原因就是,这个文件中的符号表中不存在这个符号,无法完成链接的过程,甚至连*.o文件的生成都会报错,因为无法完成符号解析的过程。

1 0
原创粉丝点击