源程序.cpp  预处理得到

预处理文件.i   编译得到

汇编文件.S    汇编得到

目标文件.o     链接得到

可执行文件

例子:main.cpp  fun.cpp fun.h

 #include <iostream>
#include "fun.h"
using namespace std; #define PI 3.14 int main()
{
print();
cout<<PI<<endl;
return ;
}
 #ifndef _FUN_H_
#define _FUN_H_
void print();
#endif
 #include <iostream>
#include "fun.h"
void print()
{
std::cout<<"hello,world"<<std::endl;
}

1. 预处理

g++ -E main.cpp -o main.i

main.i、fun.i:

      

  

  对源程序其中的伪指令(以#开头的指令)和特殊符号进行处理

(1)宏定义指令

  如 main.cpp中有 #define PI 3.14,预处理之后进行了替换

(2)条件编译指令

  #ifdef、#ifndef、#else、#elif、#endif等,根据宏定义决定对哪些代码进行处理,避免重复的引用

(3)头文件包含指令

  #include <xx.h>   #include "xx.h"等

  这些头文件中有大量的宏定义

(4)特殊符号

 printf("Date:%s,Time:%s,File:%s,Line:%d,Func:%s\n",__DATE__,__TIME__,__FILE__,__LINE__,__FUNCTION__);

  

  经过预处理,得到的.i文件没有宏定义、没有条件编译指令、没有特殊符号

2.  编译

g++ -S main.i -o main.S

  

  预处理之后的文件只有一些数字、字符串及关键字的定义,经过g++编译程序:词法分析、语法分析、优化,生成汇编文件

3. 汇编

  汇编代码汇编成机器指令

4. 链接

  多个.o文件以及库文件链接成可执行文件

  ld 一堆库文件 fun.o main.o -o a.out

  必要的库可通过  g++ -v main.o 查看

  g++ 最终通过调用 collect2来链接文件,collect2是对ld的封装

(1)静态链接

  以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件。

  将链接库的代码复制到可执行程序中

  静态链接做的事:

    ①符号解析:将目标文件符号引用和定义联系起来(因为某些符号是引用其他模块的符号)

    ②重定位:编译器、汇编器生成从地址0开始的代码和数据,链接器把每个符号定义和一个存储器位置联系起来,然后修改所有对这些符号的引用,使得从另一个位置开始执行。

(2)动态链接

  函数的定义在动态链接库或共享对象的目标文件中,在链接阶段,动态链接库只提供符号表等少量信息保证所有符号引用都有定义(不像静态链接直接复制过去),保证编译顺利通过。在可执行文件执行时,动态连接库将函数等内容映射到运行时相应进程的虚地址空间。

(3)目标文件

  ①可重定位目标文件:含二进制代码、数据,因引用了其他模块的符号而不能执行

  ②共享目标文件/动态库: .so文件

  ③可执行文件

(4)目标文件的格式 ELF文件

  ELF头:描述文件系统字长、字节序、ELF头大小、目标文件类型、目标机类型等

  .text:代码段,可执行二进制机器指令

  .rodata:只读数据段,存常量如字符串等

  .data:数据段,以明确初始化的全局数据(全局变量、静态变量),是静态内存分配

  .bss:块存储段,未被明确初始化的全局数据,这些全局数据会初始化为0,是静态内存分配

  上面的四个段会加载到内存中

  .symtab:符号表,定义和引用的函数和全局变量

  .rel.text:代码段需要重定位的信息,存储需要靠重定位修改位置的符号的汇总

  .rel.data:数据段需要重定位的信息

  .debug:gcc -g选项会生成此段

  .line:源程序的行号映射  用于调试

  .strtab:字符串表存储symtab、debug符号表中符号的名字

  查看ELF文件内容、各段大小的命令:

 readelf -a main
2 size main

  gcc命令基本选项:

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

库的生成与使用:

(1)静态库

ar rcs fun.a fun1.o fun2.o 

  选项:r:把列表中的目标文件加入到静态库

        c:若指定的静态库不存在则创建该文件

        s:更新静态文件的索引,使之包含新加入的目标文件的内容

链接时:

gcc main.c -lfun.a -o main
gcc -L. main.c -o main

  -L紧跟静态库路径

(2)动态库

gcc -shared -fPIC -o lib.so lib,c

  选项的含义:

    -shared:生成动态库

    -fPIC:生成位置无关代码

链接时:

gcc main.c ./lib.so -o main

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

可执行文件在运行时:

  除了代码段、数据段、BSS段,还有堆区和栈区

  堆区:用于动态分配内存,用 malloc、free申请和释放

              从低地址向高地址增长

              链式存储

效率比栈低

  栈区:由操作系统自动分配和释放,存储函数的参数值、局部变量的值等

从高地址向低地址增长

        连续内存

最大容量固定

C/C++源程序到可执行程序的过程的更多相关文章

  1. 转载:C/C++源代码到可执行程序的过程详解

    C/C++源代码到可执行程序的过程详解 编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格 ...

  2. 串口调试助手vc源程序及其详细编写过程

    串口调试助手vc源程序及其详细编写过程   目次: 1.建立项目 2.在项目中插入MSComm控件 3.利用ClassWizard定义CMSComm类控制变量 4.在对话框中添加控件 5.添加串口事件 ...

  3. C/C++源代码到可执行程序的过程详解

    编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序. 源代码-- ...

  4. C代码编译成可执行程序的过程

    C代码通过编译器编译成可执行代码,经历了四个阶段,依次为:预处理.编译.汇编.链接. 接下来详细讲解各个阶段 一.预处理 1.任务:进行宏定义展开.头文件展开.条件编译,不检查语法. 2.命令:gcc ...

  5. C中的预编译宏定义

     可以用宏判断是否为ARC环境 #if _has_feature(objc_arc) #else //MRC #endif C中的预编译宏定义 -- 作者: infobillows 来源:网络 在将一 ...

  6. C预编译, 预处理, C/C++头文件, 编译控制,

    在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的 ...

  7. linux装载可执行程序简析

    朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 linux中主要 ...

  8. 对于Linux内核执行过程的理解(基于fork、execve、schedule等函数)

    382 + 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 一.实验环境 win10 -> VMware -> Ubuntu1 ...

  9. Linux进程启动过程分析do_execve(可执行程序的加载和运行)---Linux进程的管理与调度(十一)

    execve系统调用 execve系统调用 我们前面提到了, fork, vfork等复制出来的进程是父进程的一个副本, 那么如何我们想加载新的程序, 可以通过execve来加载和启动新的程序. x8 ...

随机推荐

  1. C#最小化到托盘+双击托盘恢复+禁止运行多个该程序

    托盘程序的制作: 1.添加notifyIcon控件,并添加Icon,否则托盘没有图标(托盘右键菜单也可直接在属性里添加):2.响应Form的Resize或SizeChanged消息: // Hide ...

  2. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-trash

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  3. DevOps - 优势

    章节 DevOps – 为什么 DevOps – 与传统方式区别 DevOps – 优势 DevOps – 不适用 DevOps – 生命周期 DevOps – 与敏捷方法区别 DevOps – 实施 ...

  4. 软件管理-RPM命令管理:安装升级与卸载

    1.包名与包全名 包名 : 操作已经安装的软件包时,使用包名:系统会搜索var/lib/rpm中的数据库 包全名: 操作的包时没有安装的软件包时,使用包全名,而且注意路径 2.RPM安装 切换到光盘p ...

  5. HDU - 1087 Super Jumping! Jumping! Jumping!(dp)

    题意:从起点依次跳跃带有数字的点直到终点,要求跳跃点上的数字严格递增,问跳跃点的最大数字和. 分析: 1.若之前的点比该点数字小,则可进行状态转移,dp[i] = max(dp[i], dp[j] + ...

  6. 树莓派3b安装Windows10 Arm

    感谢老外的这个项目:https://github.com/WOA-Project/WOA-Deployer-Rpi 还有这个:https://uupdump.ml/ 首先从https://uupdum ...

  7. Java并发读书笔记:线程通信之等待通知机制

    目录 synchronized 与 volatile 等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 在并发编程中,保证线程同步,从而实现线程之 ...

  8. IDEA 分屏显示

    效果: 步骤: 对着某个标签页单击右键,选择Split Vertically或者Split Horizontally即可.

  9. 在Windows中安装vim

    这篇文章主要教大家如何在Windows系统下安装最好用的编辑器VIM 来自百度百科的介绍: Vim是一个类似于Vi的著名的功能强大.高度可定制的文本编辑器,在Vi的基础上改进和增加了很多特性. VIM ...

  10. Spark 内存管理

    Spark 内存管理 Spark 执行应用程序时, 会启动 Driver 和 Executor 两种 JVM 进程 Driver 负责创建 SparkContext 上下文, 提交任务, task的分 ...