文章作者:Tyan
博客:noahsnail.com  |  CSDN  |  简书

1. 引言

C++程序从源代码到可执行程序是一个复杂的过程,其流程为:源代码 --> 预处理 --> 编译 --> 优化 --> 汇编 --> 链接 --> 可执行文件,本文以一段C++代码为例,按执行顺序来描述这个过程。

2. 源代码

源代码文件分为两个,hello.hhello.cppmain.cpp,代码如下:

  • hello.hpp
#ifndef HELLO_HPP_
#define HELLO_HPP_void hello();#endif
  • hello.cpp
#include "hello.hpp"
#include <iostream>
using namespace std;void hello() {cout << "Hello, world!" << endl;
}
  • main.cpp
#include "hello.hpp"int main(int argc, char *argv[]) {hello();return 0;
}

3. 预处理

预处理是指C++程序源代码在编译之前,由预处理器(Preprocessor)对C++程序源代码进行的处理。在这个阶段,预处理器会处理以#开头的命令,处理完成之后会生成一个不包含预处理命令的纯C++文件,常见的预处理有:文件包含(#inlcude)、条件编译(#ifndef #ifdef #endif)、提供编译信息(#pragma)、宏替换(#define)等。

使用g++预处理main.cpp的命令如下:

[root@localhost:/workspace] $: g++ -E main.cpp -o main.ii

-E参数表示预处理后即停止,不进行编译,预处理后的代码送往标准输出,-o指定输出文件。输出文件main.ii的内容如下:

# 1 "main.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "main.cpp"
# 1 "hello.hpp" 1void hello();
# 2 "main.cpp" 2int main(int argc, char *argv[]) {hello();return 0;
}

4. 编译

在编译过程中,编译器主要作语法检查和词法分析。通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

编译main.ii的命令如下:

[root@localhost:/workspace] $: g++ -S main.ii

-S参数表示编译后即停止,不进行汇编。对于每个输入的非汇编语言文件,输出文件是汇编语言文件。输出文件main.s的内容如下:

        .file   "main.cpp".text.globl  main.type   main, @function
main:
.LFB0:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6subq    $16, %rspmovl    %edi, -4(%rbp)movq    %rsi, -16(%rbp)call    _Z5hellovmovl    $0, %eaxleave.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size   main, .-main.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)".section        .note.GNU-stack,"",@progbits

5. 优化

优化是在编译过程中最重要的,也是最难的。它不仅与编译技术本身有关,而且跟机器的硬件环境也有很大的关系。优化可在编译的不同阶段进行,一类优化是对中间代码的优化,这类优化不依赖于具体的计算机,另一类优化是对目标代码的优化,这类优化与机器的硬件环境有关。

g++编译器的编译优化参数为-O,分为四级,分别为-O0-O1-O2-O3,默认为-O0。各级优化后的结果如下:

# 默认优化,-O0
[root@localhost:/workspace] $: g++ -c main.cpp hello.cpp
[root@localhost:/workspace] $: nm -C main.oU __cxa_atexitU __dso_handle
000000000000007a t _GLOBAL__sub_I__Z5hellov
0000000000000022 T main
000000000000003d t __static_initialization_and_destruction_0(int, int)
0000000000000000 T hello()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::coutU std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)# 优化级别-O1
[root@localhost:/workspace] $: g++ -c -O1 main.cpp hello.cpp
[root@localhost:/workspace] $: nm -C main.oU __cxa_atexitU __dso_handle
000000000000007d t _GLOBAL__sub_I__Z5hellov
000000000000006a T main
0000000000000000 T hello()U std::ctype<char>::_M_widen_init() constU std::ostream::put(char)U std::ostream::flush()U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)U std::__throw_bad_cast()U std::cout
0000000000000000 b std::__ioinit# 优化级别-O2
[root@localhost:/workspace] $: g++ -c -O2 main.cpp hello.cpp
[root@localhost:/workspace] $: nm -C main.oU __cxa_atexitU __dso_handle
0000000000000010 t _GLOBAL__sub_I__Z5hellov
0000000000000000 T main
0000000000000000 T hello()U std::ctype<char>::_M_widen_init() constU std::ostream::put(char)U std::ostream::flush()U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)U std::__throw_bad_cast()U std::cout
0000000000000000 b std::__ioinit

6. 汇编

汇编是把汇编语言代码翻译成目标机器指令的过程。

编译main.s的命令如下:

[root@localhost:/workspace] $: g++ -c main.s

-c参数表示编译或汇编源文件,但是不作连接,编译器输出对应于源文件的目标文件。输出文件为main.o,使用nm -C main.o命令来查看文件内容,文件内容如下:

0000000000000000 T mainU hello()

7. 链接

链接是将目标文件、启动代码、库文件链接成可执行文件的过程,得到的文件可以直接执行。经过汇编之后生成的目标文件main.o是不可以直接执行的。链接命令如下:

[root@localhost:/workspace] $: g++ main.o -o main
main.o: In function `main':
main.cpp:(.text+0x10): undefined reference to `hello()'
collect2: error: ld returned 1 exit status

从上面可以看出,只链接main.o文件会报错,这是因为main.cpp引用了hello.cpp中定义的函数hello,因此需要链接文件hello.cpp才能生成可执行程序。重复上述过程,生成hello.o,链接两个文件的命令如下:

[root@localhost:/workspace] $: g++ main.o hello.o -o main

经过链接,多个文件被链接成了单一的可执行文件main,执行main程序:

[root@localhost:/workspace] $: ./main
Hello, world!

7.1 静态链接库

除了直接链接多个目标文件之外,还可以通过链接静态库生成可执行文件。静态链接库是编译器生成的一系列对象文件的集合,库中的成员包括普通函数,类定义,类的对象实例等。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。可执行文件生成之后,就不再需要静态链接库,即编译后的可执行程序不需要外部函数库的支持。但如果静态链接库发生改变,则可执行程序需要重新编译。静态链接库属于编译时链接。

我们再添加两个static.hppstatic.cpp,并修改main.cpp,内容如下:

  • static.hpp文件:
#ifndef STATIC_HPP_
#define STATIC_HPP_void test();#endif
  • static.cpp文件:
#include "static.hpp"
#include <iostream>
using namespace std;void test() {cout << "static lib" << endl;
}
  • main.cpp文件:
extern void hello();
extern void test();int main(int argc, char *argv[]) { hello();test();return 0;
}

编译汇编hello.cppstatic.cpp之后可以得到两个文件hello.ostatic.o,linux系统中的命令ar,可以将多个目标文件打包成为一个单独的文件,这个文件被称为静态库。生成静态库的命令如下:

[root@localhost:/workspace] $: ar -r libstatic.a hello.o static.o
ar: creating libstatic.a

查看libstatic.a的内容:

[root@localhost:/workspace] $: nm -C libstatic.ahello.o:U __cxa_atexitU __dso_handle
000000000000005f t _GLOBAL__sub_I__Z5hellov
0000000000000022 t __static_initialization_and_destruction_0(int, int)
0000000000000000 T hello()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::coutU std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)static.o:U __cxa_atexitU __dso_handle
000000000000005f t _GLOBAL__sub_I__Z4testv
0000000000000022 t __static_initialization_and_destruction_0(int, int)
0000000000000000 T test()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::coutU std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

通过静态链接库生成可执行程序main并执行:

[root@localhost:/workspace] $: g++ main.o libstatic.a -o main
[root@localhost:/workspace] $: ./main
Hello, world!
static lib

另一种命令方式:

[root@localhost:/workspace] $: g++ -L ./ main.cpp -lstatic -o main

Linux静态库的命名惯例是名字以三个字母lib开头并以後缀.a结束。所有的系统库都采用这种命名惯例,并且它允许通过-l(ell)选项来简写命令行中的库名。-lstatic中的-l是要求编译器在系统库目录下查找static库,staticlibstatic.a的简写。-L参数用来指定要具体的查找目录,如果缺少这个参数,则只会在系统库目录下查找static,会报错。错误如下:

[root@localhost:/workspace] $: g++ main.cpp -lstatic -o ltest
/usr/bin/ld: cannot find -lstatic

7.2 共享库

共享库(Windows叫动态链接库)是编译器以一种特殊的方式生成的对象文件的集合。对象文件模块中所有地址(变量引用或函数调用)都是相对而不是绝对的,这使得共享模块可以在程序的运行过程中被动态地调用和执行。共享库属于运行时链接。当使用共享库时,只要共享库的接口不变,共享库修改之后,不需要重新编译可执行程序。

创建dynamic.cpp,内容如下:

#include <iostream>
using namespace std;void test() {cout << "dynamic lib" << endl;
}

编译hello.cppdynamic.cpp-fpic表示生成的对象模块采用浮动(可重定位)地址,pic是位置无关代码(position independent code)的缩写。:

[root@localhost:/workspace] $: g++ -c -fpic hello.cpp static.cpp

使用-fpic与不使用-fpic生成的目标文件hello.o

# 使用-fpicU __cxa_atexitU __dso_handleU _GLOBAL_OFFSET_TABLE_
0000000000000076 t _GLOBAL__sub_I_hello.cpp
000000000000002e t __static_initialization_and_destruction_0(int, int)
0000000000000000 T hello()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::coutU std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)# 不使用-fpicU __cxa_atexitU __dso_handle
000000000000005f t _GLOBAL__sub_I__Z5hellov
0000000000000022 t __static_initialization_and_destruction_0(int, int)
0000000000000000 T hello()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))U std::ios_base::Init::Init()U std::ios_base::Init::~Init()U std::coutU std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

创建共享库dynamic.so-shared表示生成共享目标文件。:

[root@localhost:/workspace] $: g++ -shared hello.o dynamic.o -o libdynamic.so

编译main.cpp并链接共享库:

[root@localhost:/workspace] $: g++ main.cpp libdynamic.so -o main

执行main

[root@localhost:/workspace] $: ./main
./main: error while loading shared libraries: dynamic.so: cannot open shared object file: No such file or directory

报错是因为当前工作目录可能不在共享库的查找路径中,因此需要将当前目录添加到环境变量LD_LIBRARY_PATH中:

[root@localhost:/workspace] $: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
[root@localhost:/workspace] $: ./main
Hello, world!
dynamic lib

查看链接静态库和共享库生成的两个可执行main文件:

# 共享库
[root@localhost:/workspace] $: nm -C main
000000000060103c B __bss_start
000000000060103c b completed.6354
0000000000601038 D __data_start
0000000000601038 W data_start
0000000000400650 t deregister_tm_clones
00000000004006c0 t __do_global_dtors_aux
0000000000600dd8 t __do_global_dtors_aux_fini_array_entry
00000000004007b8 R __dso_handle
0000000000600de8 d _DYNAMIC
000000000060103c D _edata
0000000000601040 B _end
00000000004007a4 T _fini
00000000004006e0 t frame_dummy
0000000000600dd0 t __frame_dummy_init_array_entry
00000000004008e8 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_w __gmon_start__
00000000004005a8 T _init
0000000000600dd8 t __init_array_end
0000000000600dd0 t __init_array_start
00000000004007b0 R _IO_stdin_usedw _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTable
0000000000600de0 d __JCR_END__
0000000000600de0 d __JCR_LIST__w _Jv_RegisterClasses
00000000004007a0 T __libc_csu_fini
0000000000400730 T __libc_csu_initU __libc_start_main@@GLIBC_2.2.5
000000000040070d T main
0000000000400680 t register_tm_clones
0000000000400620 T _start
0000000000601040 D __TMC_END__U test()U hello()# 静态库
[root@localhost:/workspace] $: nm -C main
000000000060105c B __bss_start
0000000000601170 b completed.6354U __cxa_atexit@@GLIBC_2.2.5
0000000000601058 D __data_start
0000000000601058 W data_start
00000000004007b0 t deregister_tm_clones
0000000000400820 t __do_global_dtors_aux
0000000000600de8 t __do_global_dtors_aux_fini_array_entry
0000000000400a08 R __dso_handle
0000000000600df8 d _DYNAMIC
000000000060105c D _edata
0000000000601178 B _end
00000000004009f4 T _fini
0000000000400840 t frame_dummy
0000000000600dd0 t __frame_dummy_init_array_entry
0000000000400c40 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
0000000000400960 t _GLOBAL__sub_I__Z4testv
00000000004008ec t _GLOBAL__sub_I__Z5hellovw __gmon_start__
00000000004006d0 T _init
0000000000600de8 t __init_array_end
0000000000600dd0 t __init_array_start
0000000000400a00 R _IO_stdin_usedw _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTable
0000000000600df0 d __JCR_END__
0000000000600df0 d __JCR_LIST__w _Jv_RegisterClasses
00000000004009f0 T __libc_csu_fini
0000000000400980 T __libc_csu_initU __libc_start_main@@GLIBC_2.2.5
000000000040086d T main
00000000004007e0 t register_tm_clones
0000000000400780 T _start
0000000000601060 D __TMC_END__
00000000004008af t __static_initialization_and_destruction_0(int, int)
0000000000400923 t __static_initialization_and_destruction_0(int, int)
0000000000400901 T test()
000000000040088d T hello()U std::ostream::operator<<(std::ostream& (*)(std::ostream&))@@GLIBCXX_3.4U std::ios_base::Init::Init()@@GLIBCXX_3.4U std::ios_base::Init::~Init()@@GLIBCXX_3.4
0000000000601060 B std::cout@@GLIBCXX_3.4U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@@GLIBCXX_3.4
0000000000601171 b std::__ioinit
0000000000601172 b std::__ioinitU std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@@GLIBCXX_3.4

8. 可执行文件

可执行文件指的是可以由操作系统进行加载执行的文件。在不同的操作系统环境下,可执行程序的呈现方式不一样。例如上面生成的main就是Linux系统下的可执行文件,windows系统下的可执行文件一般为*.exe

参考资料

  1. https://wiki.ubuntu.org.cn/Compiling_Cpp
  2. https://tech.meituan.com/2015/01/22/linker.html
  3. http://notes.maxwi.com/3416/06/05/source-to-program/
  4. http://www.ruanyifeng.com/blog/2014/11/compiler.html
  5. https://blog.csdn.net/zhengqijun_/article/details/51881149
  6. https://www.cnblogs.com/Goldworm/archive/2012/05/21/2511910.html
  7. https://juejin.im/entry/5c0d23b35188253b7e7480db
  8. https://www.zhihu.com/question/280665935
  9. http://www.shanghai.ws/gnu/gcc_1.htm
  10. https://wiki.ubuntu.org.cn/Compiling_C
  11. https://www.cnblogs.com/sunsky303/p/7731911.html

C++源文件到可执行程序相关推荐

  1. 19.04.02笔记

    用户权限: su 切换用户账户 格式: [su 用户名 ] [su] 切换到root [su root] 切换到root [su -] 切换到root用户 同时切换到root目录 添加组: [grou ...

  2. linux g++ gcc编译c++哪个好,linux g++编译c++

    单个源文件生成可执行程序 编译器 g++ 通过检查命令行中指定的文件的后缀名可识别其为 C++ 源代码文件.编译器默认的动作:编译源代码文件生成对象文件(object file),链接对象文件和 li ...

  3. Go基础系列:构建go程序

    hello world 从一个简单的程序开始解释,将下面的内容放进test.go文件中,路径随意: package mainimport ("fmt" )func main() { ...

  4. Compiling Cpp(zz)

    zz: http://wiki.ubuntu.org.cn/Compiling_Cpp C++ 编程中相关文件后缀 .a 静态库 (archive) .C .c .cc .cp .cpp .cxx . ...

  5. Compiling Fortran

    Compiling Fortran 目录 [隐藏] 1 写在前面 2 Fortran 编程中相关文件后缀 3 单个源文件生成可执行程序 4 多个源文件生成可执行程序 5 生成汇编代码 6 编译预处理 ...

  6. 速学c++(1)-c++简介

    c++ 简介 本系列基于最新的C++标准(11/14/17)讲解. c++标准是由ISO(国际标准化组织)以及多个国家标准组织(如INCITS(美国国家信息技术标准委员会).BSI(英国标准协会).D ...

  7. C++检测步骤与示例

    检测步骤与示例 步骤 编译源文件获取可执行程序 在valgrind下,运行可执行程序 1)使用未初始化内存 2)内存越界访问 3)内存覆盖 4)动态内存管理错误 5)内存泄露 确定的内存泄露 起的内存 ...

  8. gcc编译C++程序

     单个源文件生成可执行程序 下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码:  /* helloworld.cpp */ #include <iostr ...

  9. ubuntu下C编程,编译基础( 转)

    buntu下C编程,编译基础 C 编程中相关文件后缀 .a 静态库 (archive) .c C源代码(需要编译预处理) .h C源代码头文件 .i C源代码(不需编译预处理) .o 对象文件 .s ...

最新文章

  1. iPhone必崩溃bug曝光!这个WiFi水太深谁也把握不住
  2. excel取整函数_Excel中的这些烧脑问题,你遇到过几种?
  3. 动态加载 回显_ElementUI cascader级联动态加载及回显
  4. Asp.Net Core 快速邮件队列设计与实现
  5. linux awk 常见字符串处理
  6. OkHttp协议介绍以及文件下载和上传+OkHttp协议封装+OkHttp拦截器____SpringBoot——集成Okhttp3
  7. Linux基础学习五:软件的相关安装(JDK,Tomcat,Yum)
  8. 线性表:4.结合顺序表和链表——静态链表及C语言实现
  9. linux中lamp架构搭建,Linux LAMP架构平台搭建
  10. RHEL5.8安装telnet服务
  11. VS2012统计代码量
  12. 60. MySQLi 扩展拾遗
  13. Redis 入门指南
  14. 解决Gitbub打开或者下载很慢
  15. HDU 2370 Convert Kilometers to Miles
  16. 一文了解复旦大学NLP实验室的14篇EMNLP 2022长文内容
  17. html 单元格被撑开_tabletd宽度被撑开的问题。棘手~_html/css_WEB-ITnose
  18. NTP网络时钟同步协议对计算机网络数据的重要性
  19. Java九十条经验法则之第一条:用静态工厂方法代替构造器
  20. 微信小程序版“美图秀秀”源码

热门文章

  1. 已知某分页系统,主存容量为 64K 字节,页面大小为 1K,对一个 4 页大的作 业,其 0、1、2、3 页分别被分配到主存的 2、4、6、7 块中,试:将十进制的逻 辑地址 1023、2500、35
  2. 在头条号和西瓜视频发布视频,播放量20万,却是零收益?
  3. 行走在数据库上的行癫(三)
  4. 大数据的产业链分析,大数据完整的产业链构成
  5. hbase —— Dead Region Servers
  6. 学生DW静态网页设计—西安旅游-高质量(9页) HTML+CSS+JavaScript 学生DW网页设计
  7. sxt_(015)_request处理表单数据及乱码
  8. 记一次ARM-鲲鹏服务器读写parquet报错解决过程
  9. 图解强化学习 原理 超详解 (一)
  10. 快排C语言三种实现方法(大同小异)