底层思维,是指程序在硬件上如何编译存储运行的。
- gcc编译
$ gcc main.c //编译C程序,默认生成a.out执行文件
//gcc -v main.c 显示详细的编译过程信息
//如果是C++文件(main.cpp),把gcc改成g++即可
$ ./a.out //运行程序
- gdb调试
$ gcc -g main.c //编译生成带有调试信息的执行文件a.out
$ gdb a.out //调试程序
$ l //list: 查看源码(默认10行)
//list 8 查看8行附近的代码
//list main 查看函数名附近的码
$ b 5 //break: 在第5行设置断点
$ r //run: 运行到断点处(如无则运行直到结束)
$ s //step: 单步执行(如果是函数则进入)
$ n //next: 单步跳过(如果是函数则跳过)
$ p a //print: 查看变量内容
// p &a 查看变量a的地址
// p &b+1 b变量的地址根据数据类型递增(int型 会加4,char型则加1)
// p *(&b+1)
// p a[1]
// p *p1 查看p1指针里的内容
// p add(4,7) 调用函数add 并打印返回值
$ p/t b // t 二进制显示 变量的值
// x 按十六进制格式显示变量。
// d 按十进制格式显示变量。
// u 按十六进制格式显示无符号整型。
// o 按八进制格式显示变量。
// a 按十六进制格式显示变量。
// c 按字符格式显示变量。
// f 按浮点数格式显示变量。
$ x &a //显示某地址的内存值
//例如 float a = 0.45; 的内存值为
// 0x7fffffffdf8c: 00111110111001100110011001100110
上下键 //可查看历史命令
$ bt //查看堆栈信息 (如嵌套调用多个函数时)
$ c //continue: 继续执行程序(直到下一个断点或者结束)
$ q //quit: 退出gdb调试
$ dmesg //查看内核信息
如何编译生成执行文件?
生成执行文件的过程:main.c -> 预处理 -> 编译 -> 汇编 -> 连接 -> a.out
$ gcc main.c fun.c //编译多个文件,生成执行文件a.out
$ ./a.out //运行程序
- 预处理
预处理执行的功能:
(1)删除“#define”并展开所定义的宏
(2)处理所有条件预编译指令,如“#if”,“#ifdef”, “#endif”等
(3)插入头文件到“#include”处,可以递归方式进行处理
(4)删除所有的注释“//”和“/* */”
(5)添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
(6)保留所有#pragma编译指令(编译器需要用)
经过预编译处理后,得到的是预处理文件(如,hello.i) ,它还是一个可读的文本文件 ,但不包含任何宏定义,上面所说的第3条插入头文件到“include”处,可以理解为将头文件里面的内容进行展开
$ gcc -E main.c -o main.i //停在预处理阶段(生成 main.i文件)
//发现宏被替换了,条件编译生效
- 编译
编译过程就是将预处理后得到的预处理文件(如 main.i)进行词法分析、语法分析、语义分析、优化后,生成汇编代码文件。用来进行编译处理的程序称为编译程序(编译器,Compiler)
经过编译后,得到的汇编代码文件(如 main.s)还是可读的文本文件,CPU无法理解和执行它,不要着急,接下来进行下一步汇编命令的执行过程
$ gcc -S main.c //停在编译阶段(生成汇编文件 main.s)
- 汇编
首先我们先了解下汇编代码文件(由汇编指令构成)称为汇编语言源程序,其实就是上面编译过程结束之后生成的.s文件,这个文件就是汇编代码文件,该文件是有一条条汇编指令构成,汇编的作用就是讲这一条条汇编指令转换成对应的机器码执行。
汇编结果是一个可重定位目标文件(如,main.o),其中包含的是不可读的二进制代码,必须用相应的工具软件来查看其内容
“汇编程序(汇编器)用来将汇编语言源程序转换为机器指令序列(机器语言程序) 汇编指令和机器指令一一对应,前者是后者的符号表示,它们都属于机器级指令,所构成的程序称为机器级代码,汇编的过程比较简单,只需要将相应的汇编指令翻译成对应的机器指令即可,没有什么复杂的变化。”
$ gcc -c main.c //停在汇编阶段(生成目标文件 main.o )
$ vim main.o //16进制显示 二进制文件
//在命令行模式下输入 :%!xxd
- 链接
预处理、编译和汇编三个阶段针对一个模块(一个*.c文件)进行处理,得到对应的一个可重定位目标文件(一个*.o文件),但是在程序的编写过程中,我们都是多个.c文件的,这样经过上面的预处理,编译汇编的过程之后我们得到的也是多个.o(可重定位目标文件),但是我们在最终执行的时候是只有一个可执行文件的,这个过程就是链接的目的了。
链接过程将多个可重定位目标文件合并以生成可执行目标文件,在最后一步连接的过程中,我们不只是要hello.o而且还需要printf.o(代码中含有printf函数),链接就是将这连个.o合并为一个生成可执行目标文件。
$ gcc -c fun.c
$ gcc main.o fun.o //链接目标文件或库文件(.a .so),生成执行文件 a.out(.bin .exe)
//独立的目标文件都是从0地址开始,需要链接来重新合并安排地址
//链接异常 会报 collect2: error: ld returned 1 exit status
$ objdump -d a.out //查看反汇编代码
//可查看 objdump -d fun.o 等看链接过程是如何把代码嵌入式到main.o中
参考资料
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)