文章目录

  • 程序员的自我修养
  • 1.基础知识
  • 2.编译和链接
    • 2.1被隐藏的过程
      • 2.1.1预编译
      • 2.1.2 编译
      • 2.1.3 汇编
      • 2.1.4 链接
    • 2.2 编译器做了什么
      • 2.2.1 词法分析
      • 2.2.2 语法分析
      • 2.2.3 语义分析
      • 2.2.4 中间语言生成
      • 2.2.5 目标代码生成与优化
    • 2.3 链接器年龄比编译器长
    • 2.4 模块拼装——静态链接
  • 3.目标文件中有什么
    • 3.1目标文件的格式
    • 3.2目标文件是什么样的
    • 3.3挖掘SimpleSection.o
      • 3.3.1代码段
      • 3.3.2数据段和只读数据段

程序员的自我修养

1.基础知识

2.编译和链接

2.1被隐藏的过程

IDE一般都将编译和链接的过程一步完成,通常将这 种编译和链接合并到一起的过程称为构建(Build)。即使使用命令行 来编译一个源代码文件,简单的一句“gcc hello.c”命令就包含了非常复 杂的过程。

#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
hello.c

编译执行:

$gcc hello.c

$./a.out

Hello World

上述过程可以分解为4个步骤:

  1. 预处理(Prepressing)
  2. 编译(Compilation)
  3. 汇编(Assembly)
  4. 链接(Linking)

2.1.1预编译

$gcc –E hello.c –o hello.i$cpp hello.c > hello.i

将源代码文件hello.c和相关的头文件(stdio.h)等被预编译器cpp预编译成一个.i文件

2.1.2 编译

$gcc –S hello.i –o hello.s

把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件

2.1.3 汇编

$as hello.s –o hello.o

将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎 都对应一条机器指令,目标文件

2.1.4 链接

$ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-linuxgnu/4.1.3/crtbeginT.o -L/usr/lib/gcc/i486-linux-gnu/4.1.3 -L/usr/lib - L/lib hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i486- linux-gnu/4.1.3/crtend.o /usr/lib/crtn.o

要将一大堆文件链接起来才可以得到“a.out”,即最终的可执行文件

2.2 编译器做了什么

编译过程一般可以分为6步:

  1. 扫描
  2. 语法分析
  3. 语义分析
  4. 源代码优化
  5. 代码生成
  6. 目标代码优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CIRUgEVF-1671542167853)(程序员的自我修养_img/image-20221201203506799.png)]

示例代码:

array[index] = (index + 4) * (2 + 6)CompilerExpression.c

2.2.1 词法分析

源代码程序被输入到扫描器(Scanner),扫描器简单地进行词法分析

词法分析产生的记号分为:关键字、标识符、字面量 (包含数字、字符串等)和特殊符号(如加号、等号)

同时也将标识符存放到符号表,将数字、字符串常量存放到文字表等

2.2.2 语法分析

语法分析器(Grammar Parser)将对由扫描器产生的记号进行语 法分析,从而产生语法树(Syntax Tree)

2.2.3 语义分析

编译器所能分析的语义是静态语义 (Static Semantic),所谓静态语义是指在编译期可以确定的语义,与之对应的动态语义(Dynamic Semantic)就是只有在运行期才能确定的语义。

经过语义分析阶段以后,整个语法树的表达式都被标识了类型,如果有 些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。

语义分析器还对符号表里的符号类型也做了更新

2.2.4 中间语言生成

直接在语法树上作优化比较困难,所以源代码优化器往往将整个语法树转换成中间代码 (Intermediate Code),它是语法树的顺序表示,其实它已经非常接近目标代码了

上面的例子中的语法树可以被翻译成三地址码:

t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
array[index] = t3

为了使所有的操作都符合三地址码形式,这里利用了几个临时变量:t1、t2和t3,优化后的三地址码:

t2 = index + 4
t2 = t2 * 8
array[index] = t2

中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对 于一些可以跨平台的编译器而言,它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。

2.2.5 目标代码生成与优化

编译器后端主要包括代码生成器(Code Generator)和目标代码优化器 (Target Code Optimizer)

  1. 代码生成器将中间代码转换成目标机器代码,这个过程十分依赖于目标机器,因为 不同的机器有着不同的字长、寄存器、整数数据类型和浮点数数据类型等。

代码生成器可能会生成下面的代码序列(用x86的汇编语言表示):

movl index, %ecx ; value of index to ecx
addl $4, %ecx ; ecx = ecx + 4
mull $8, %ecx ; ecx = ecx * 8
movl index, %eax ; value of index to eax
movl %ecx, array(,eax,4) ; array[index] = ecx
  1. 目标代码优化器对上述的目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等

优化后:

movl index, %edx
leal 32(,%edx,8), %eax
movl %eax, array(,%edx,4)

2.3 链接器年龄比编译器长

2.4 模块拼装——静态链接

链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接;即把一些指令对其他符号地址的引用加以修正

链接过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位 (Relocation)等这些步骤

最基本的静态链接:每个模块的源代码文件(如.c)文 件经过编译器编译成目标文件(Object File,一般扩展名为.o或.obj), 目标文件和库(Library)一起链接形成最终可执行文件

3.目标文件中有什么

3.1目标文件的格式

3.2目标文件是什么样的

目标文件包含:机器码指令、数据、符号表、调试信息、字符串等。

将这些内容按不同的属性称为段。分为:

  1. 代码段(.code/.text):机器指令
  2. 数据段
    • (.data):已初始化的全局变量,局部静态变量数据
    • (.bss):未初始化的全局变量,局部静态变量数据
    • (.rodata):只读数据

3.3挖掘SimpleSection.o

int printf( const char* format, ... );
int global_init_var = 84;
int global_uninit_var;
void func1( int i )
{printf( "%d\n", i );
}
int main(void)
{static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1( static_var + static_var2 + a + b );
return a;
}/*
* SimpleSection.c
*
* Linux:
* gcc -c SimpleSection.c
*
* Windows:
* cl SimpleSection.c /c /Za
*/

查看目标文件:

$ objdump -h SimpleSection.o
SimpleSection.o: file format elf32-i386
Sections:
Idx Name Size    VMA      LMA      File off Algn
0 .text 0000005b 00000000 00000000 00000034 2**2CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 00000000 00000000 00000090 2**2CONTENTS, ALLOC, LOAD, DATA
2 .bss  00000004 00000000 00000000 00000098 2**2ALLOC
3 .rodata 00000004 00000000 00000000 00000098 2**0CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 00000000 00000000 0000009c 2**0CONTENTS, READONLY
5 .note.GNU-stack 00000000 00000000 00000000 000000c6 2**0CONTENTS, READONLY

$ size SimpleSection.o   // 查看ELF文件的代码段、数据段和BSS段的长度(dec表示3个段长度的和的十进制,hex表示长度和的十六进制)
text data bss dec hex filename
95   8    4   107 6b  SimpleSection.o

3.3.1代码段

$ objdump -s -d SimpleSection.o //-s参数可以将所有段的内容以十六进制的方式打印出来 //-d ”参数可以将所有包含指令的段反汇编。Contents of section .text:
0000 5589e583 ec088b45 08894424 04c70424 U......E..D$...$
0010 00000000 e8fcffff ffc9c38d 4c240483 ............L$..
0020 e4f0ff71 fc5589e5 5183ec14 c745f401 ...q.U..Q....E..
0030 0000008b 15040000 00a10000 00008d04 ................
0040 020345f4 0345f889 0424e8fc ffffff8b ..E..E...$......
0050 45f483c4 14595d8d 61fcc3 E....Y].a..00000000 <func1>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 8b 45 08 mov 0x8(%ebp),%eax
9: 89 44 24 04 mov %eax,0x4(%esp)
d: c7 04 24 00 00 00 00 movl $0x0,(%esp)
14: e8 fc ff ff ff call 15 <func1+0x15>
19: c9 leave
1a: c3 ret0000001b <main>:
1b: 8d 4c 24 04 lea 0x4(%esp),%ecx
1f: 83 e4 f0 and $0xfffffff0,%esp
22: ff 71 fc pushl -0x4(%ecx)
25: 55 push %ebp
26: 89 e5 mov %esp,%ebp
28: 51 push %ecx
29: 83 ec 14 sub $0x14,%esp
2c: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
33: 8b 15 04 00 00 00 mov 0x4,%edx
39: a1 00 00 00 00 mov 0x0,%eax
3e: 8d 04 02 lea (%edx,%eax,1),%eax
41: 03 45 f4 add -0xc(%ebp),%eax
44: 03 45 f8 add -0x8(%ebp),%eax
47: 89 04 24 mov %eax,(%esp)
4a: e8 fc ff ff ff call 4b <main+0x30>
4f: 8b 45 f4 mov -0xc(%ebp),%eax
52: 83 c4 14 add $0x14,%esp
55: 59 pop %ecx
56: 5d pop %ebp
57: 8d 61 fc lea -0x4(%ecx),%esp
5a: c3 ret

3.3.2数据段和只读数据段

程序员的自我修养笔记(持续更...)相关推荐

  1. 程序员的自我修养笔记之装载

    可执行文件的装载与进程 介绍ELF文件在Linux下的装载过程,探寻可执行文件装载的本质 什么是进程的虚拟地址空间 为什么进程要有自己独立的虚拟地址空间 几种装载方式 进程虚拟地址空间的分布情况 进程 ...

  2. 程序员的自我修养笔记 2

    编译的基本流程 构建:编译和链接合并到一起的过程. 整个编译的过程可以分为4个阶段: 预处理:主要是处理#符号后面的内容,比如展开宏定义和处理ifndef等:还包括删除掉所有的注释等.还有一个重要的步 ...

  3. 程序员的自我修养笔记3 内存管理

    程序的内存布局 大多数的操作系统中,都会有一部分空间是程序无法访问的,这是内核空间,专门留给操作系统内核使用的.一个进程的空间中,有4个部分,分别是: 栈:维护函数调用的上下文,一般分配在用户空间的最 ...

  4. 程序员的自我修养笔记(一)

    一..牵扯到"计算"这个概念的都可以成为计算机,我们研究的主要是兼容x86指令集的32位CPU个人计算机. 1. 早期:CPU和内存的频率差不多,连接在同一个总线上. I/O设备效 ...

  5. 《程序员的自我修养》学习笔记

    程序员的自我修养 第一章 谈职业生涯 1.程序员应该是那些不断追求更高技术,并有着自己产品梦的工匠.当你通过对自己技术不断打磨,一次又一次做出那些优秀产品的时候,你会发现自己不再是他人口中的码农或是屌 ...

  6. 《程序员的自我修养》整理笔记

    特此声明:<程序员的自我修养>是陈逸鹤先生为初入程序江湖之人所编写的一本优质的生涯指导书,本文为此书的读后感和笔记 职业生涯 一.写给年轻程序员的10点启示 正确的认识自己 比一般人更努力 ...

  7. 程序员的自我修养——读《软技能-代码之外的生存指南》笔记

    我记得曾经读过俞甲子的<程序员的自我修养--链接.加载和库>,当时就觉得这个书名起的不太合适,有点不合主题,因为这本书主要讲述链接库的事情,我认为这个是编译器的一部分,是作为程序员要掌握的 ...

  8. 程序员的自我修养读书笔记-1

    前序:作为一个马上就要工作的非科班本科生,前段时间为了找工作,有针对性的学习了一些编程语言,数据结构,网络方面的知识,学的非常浅,非常杂乱,存粹是为了应对找工作.现在空下来了,想着以后应该就是走程序员 ...

  9. 《程序员的自我修养》阅读笔记(一)

    这本书的初版是2009年出版的,但是其理论知识放在2021年仍然管用,十多年计算机的框架并没有发生大的变化.这本书讲解了一门编程语言是如何诞生的,通过这本书,可以理解一些操作系统和软件的运行机制和原理 ...

最新文章

  1. OpenAI开源机器人模拟Python库mujoco-py:可高效处理并行模拟
  2. php7的foreach遍历数组,PHP中使用foreach遍历三维数组
  3. OpenKruise:解放 DaemonSet 运维之路
  4. FM之SO_DOCUMENT_SEND_API1
  5. System Center Virtual Machine Manager 2012 安装
  6. JS 实现两表格里的数据来回 转移
  7. ubuntu上安装 ibus Google拼音输入法解决中文输入问题
  8. LibreELEC(kodi)基本设置
  9. keygen是怎么写出来的。
  10. 战斗在 VUCA 时代
  11. Apk脱壳圣战之---如何脱掉“梆梆加固”的保护壳
  12. 【二】gym初次入门一学就会---代码详细解析简明教程----平衡杆案例
  13. androidpn客户端深析
  14. JD2016版首页改版前端总结(转载整理)
  15. 三无真香级浏览器——Alook
  16. emacs chinese manual
  17. Python程序员私活来源:大学同窗开公司,伸手就要爬资料
  18. Android系统的定制
  19. 【开源项目】Imagine图片压缩工具
  20. Redis 运维 二、

热门文章

  1. 一元线性回归分析一和 相关数学知识的定义
  2. mysql 性能 数据 指标_MySQL数据库:三个关键性能指标
  3. StackGAN论文解读
  4. Python file文件常用方法(二):开关文件-open()、close()
  5. Frida JavaScript API学习
  6. 中国历史中最具争议性的美女
  7. ceph提供iscsi
  8. 使用maven创建一个简单的gwt工程
  9. 玩转MySQL:14000字来详解库表设计
  10. android 判断是否wifi,android 判断是否连接wifi