C++ 编译链接过程
1、预处理器:将.c 文件转化成 .i文件,使用的gcc命令是:gcc –E,对应于预处理命令cpp;
2、编译器:将.c/.h文件转换成.s文件,使用的gcc命令是:gcc –S,对应于编译命令 cc –S;
3、汇编器:将.s 文件转化成 .o文件,使用的gcc 命令是:gcc –c,对应于汇编命令是 as;
4、链接器:将.o文件转化成可执行程序,使用的gcc 命令是: gcc,对应于链接命令是 ld;
5、加载器:将可执行程序加载到内存并进行执行,loader和ld-linux.so。
由不同编译器创建的二进制模块很可能无法正确链接,在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的(或同一标准的编译器)
编译器前端和后端
编译器粗略分为词法分析,语法分析,类型检查,中间代码生成,代码优化,目标代码生成,目标代码优化。
把中间代码生成及之前阶段划分问编译器的前端,那么后端与前端是独立的
。后端只需要一种中间代码表示,可以是三地址代码或四元式等,而这些都与前端生成的方式无关。
编译器的前端将不同的高级编程语言经过词法分析、语法分析转化为与前端语言无关的中间表示。有了与前端语言无关的中间表示,一个编译器就可以支持编译多种高级语言。
按照这个分类,自己动手编写编译器,可以不必从头开始了。使用LLVM,我们可以做一个前端,然后和LLVM后端对接。
大端(Big Endian/Big Endiayin)
数据的高字节部分放在内存中的低字节地址,数据的低字节部分放在内存中的高字节地址
例如:
小端(Little Endian)
数据的高字节部分放在内存中的高字节地址,数据的低字节部分放在内存中的低字节地址
例如:
类型转换
不同基本操作数进行运算会隐式的将内存占用较少的类型转换成占用较多的操作数类型
尽量不要使用隐式类型转换,即使是隐式的数据类型转换是安全的,因为隐式类型数据转换降低了程序的可读性。尽量使用强制(显示)类型转换
将占用空间大的数据的指针强制转换成占用空间小的数据的指针,会发生内存截断,将占用空间小的数据的指针强制转换成占用空间大的数据的指针,会发生内存扩张。例如
1 | doule d5 = 100.0; |
内存截断:
内存扩张:
发生内存扩张时,往扩张的内存写数据会发生运行时错误
标识符
标准C语言规定,编译器只取前31个字符作为有效的标示符,而标准C++取前255个字符作为有效的标识符引用类型
引用变量在声明时必须被赋值,引用变量和被引用的变量的地址是相同的
1 | int a = 1; |
常量
对于指针类型和引用类型,将非const值赋值给const变量是合法的,但是反之则是非法的
1 | int a = 10; |
字面常量
字面常量:直接出现的数字、字符、字符串等,只存在基本数据类型的字面常量,字面常量只能引用,不能修改。除字符串外,你无法取一个字面常量的地址,例如:
1 | int *p = &5;//语句错误 |
并且当你试图通过常量字符串的地址修改其中的字符时就会报告“只读错误”(此错误为运行时错误)例如:
1 | char *pChar = "abcdef"; |
符号常量
符号常量分为用#define定义的宏常量和用const定义的常量。
使用#define宏定义的符号常量在进入编译阶段前就已经被替代为所代表的字面常量了,因此宏常量本质上是字面常量。
取const符号常量的地址或引用时,对于基本数据类型的const常量,编译器会重新在内存中创建一个拷贝,你通过其地址访问到的时这个拷贝而非原始的符号常量,例如:1 | const long lng = 10; |
1 | struct Integer { |
指针运算
指针自增(++),表示它指向了序列中的后一个元素
指针自减(—),表示它指向了序列中的前一个元素
指针加一个正整数i,表示它向后递进i个元素
指针减一个正整数i,表示它向前递进i个元素
两个同类型指针相减,表示计算它们之间的元素个数
指针加/减一个正整数i,其含义并不是在其值上直接加/减i,还要包含所指对象的字节数信息。
不能对void*类型指针使用“*”来取所指的变量
数组
任何数组,不论是静态声明的还是动态创建的,其所有元素在内存中都是连续字节存放的,也就是说保存在一大块连续的内存区中,std::vector的所有元素对象在内存中也是连续存放的。
创建动态二维数组的方法
1 | int **p = new int*[n]; |
一个很不符合逻辑的表达式
1 | int a = 0; |
最后一个表达式没有任何实际意义,它一般是错误将判断相等表达式“==”错误写成“=”的结果
endl
endl是一个函数模板,它实例化之后变成一个模板函数,其作用是插入换行符并刷新输出流
。其中刷新输出流指的是将缓冲区的数据全部传递到输出设备并将输出缓冲区清空。
避免头文件循环引用
1 |
|
宏
#ifdef 只判断是否定义了某个宏
#if 不仅判断是否定义了宏,而且还判断宏是否为真
#undef 用于取消定义的宏
利用宏转字符串
1 |
在宏定义汇总使用#
表示将符号转换为对应的字符串,例如使用上面的宏TO_STR(test)
的结果就是"test"
。
利用宏链接符号
1 |
在宏定义中使用##
表示连接,例如使用上面的宏Contact(test)
的结果就是test_1
。