编译和调试
C/C++程序编译过程
- C/C++程序编译过程就是把C/C++代码百年城可执行文件的过程, 该过程分为4步
- 预处理阶段
- 进行宏展开和宏替换
- 处理条件编译指令, 如#ifdef, #endif等
- 去掉注释
- 添加行号和文件名标识
- 保留#pargma编译器指令(#Pragma命令将设定编译器的状态或者是指示编译器完成一些特定的动作)
- 编译阶段
- 编译程序所要作得工作就是通过词法分析, 语法和语义分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
- 代码优化
- 重点关注函数压栈方式的编译处理
- __cdecl是C DECLaration的缩写, 表示C语言默认的函数调用方法: 所有参数从右到左依次入栈. 这些参数由调用者清除,称为手动清栈. 被调用函数不需要求调用者传递多少参数, 调用者传递过多或者过少的参数. 甚至完全不同的参数都不会产生编译阶段的错误。
- _stdcall 是StandardCall的缩写, 是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话, 最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除, 使用的指令是 retnX,X表示参数占用的字节数. CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈. 函数在编译的时候就必须确定参数个数. 并且调用者必须严格的控制参数的生成,不能多, 不能少, 否则返回后会出错。
- 汇编阶段
- 汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程
- 对于被翻译系统处理的每一个C语言源程序, 都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
- 目标文件由段组成. 通常一个目标文件中至少有两个段: 代码段和数据段
- 链接阶段
- 将所有的目标文件代码拼接且重定位符号地址, 生成可执行文件
- 两种链接方式: 动态链接和静态链接
动态链接和静态链接区别
-
静态链接
- 在编译时静态库和程序链接
- 一般命名为:
libxxx.a
- 运行时,可执行目标文件已经装载完毕,速度快
- 但是多个程序需要静态库时每个都会复制一份,造成内存浪费
- 更新后需要重新编译
-
动态链接
-
在运行时链接
-
一般命名:
libxxx.so
-
运行时加载链接, 速度相对慢
-
运行时多个程序共享同一份动态库, 不会造成内存浪费
-
易更新, 无需重新编译
-
内存管理
new/delete和malloc/free区别
- new是C++关键字,需要编译器支持. 而malloc是C语言库函数
- new失败时, 会抛出bad_alloc异常. malloc会返回NULL
- new执行时, 先分配内存, 再调用类的构造函数初始化, malloc只会分配内存
- new无需指定分配内存大小, 编译器会根据类型信息自行计算, malloc需要在参数里指出分配内存的大小
- new成功会直接返回所分配的对象的指针, 而malloc只会返回void指针, 需要转化才能得到想要的对象的指针
- C++允许重载new/delete操作符, 而malloc不允许重载, new从自由存储区上为对象动态分配内存空间, malloc从堆上分配内存
C++中有几种类型的new
-
plain new: 普通new, 特点在new和malloc区别里说了
-
nothrow new: 在空间分配失败时不会抛出异常, 而是返回NULL. 使用:
char *p=new(nothrow) char[10e11]
-
new operator: 只做两件事:(1)调用operator new (2)调用类的构造函数
-
operator new: 可以重载, 实际以标准C malloc()完成
-
placement new: 在一块已经分配成功的内存上重新构造对象或对象数组. 不会出现内存分配失败, 因为他只调用构造函数而不会分配内存. 用placement new构造和对象数组, 要显式调用它们的析构函数来销毁, 而不要用delete[], 因为构造起来的对象或数组大小不一定等于原来内存的大小, 用delete会造成内存泄漏或运行时出现错误. 使用:
#include <iostream> #include <string> using namespace std; class ADT{ int i; int j; public: ADT(){ i = 10; j = 100; cout << "ADT construct i=" << i << "j="<<j <<endl; } ~ADT(){ cout << "ADT destruct" << endl; } }; int main(