C语言
编译过程
预处理: 展开头文件及宏定义
gcc -E -I./inc test.c -o test.i
cpp test.c -I./inc -o test.i
编译: 将预处理的代码翻译成汇编代码
gcc -S -I./inc test.c -o test.s
汇编: 将汇编代码翻译成机器码, 这一步生成二进制格式的目标文件
as test.s -o test.o
链接: 将目标文件和库文件链接成最后的可执行程序
ld -o test.out test.o inc/mymath.o ...libraries...
内存布局
栈区
局部变量, 编译器在编译时已经确定了栈的大小
堆区
malloc分配的内存, 程序员自己控制 分配与释放
数据区
全局区(静态区)
如果已初始化, 放在DATA段
如果未初始化, 放在BSS段, 这里只保存必要的大小信息, 不占用可执行程序的大小, 加载程序时分配内存
常量区
常量字面量
代码区
函数定义
大小端 位域
大小端:
intel芯片用的是小端, 就是内存是递增的, 数据是按照字节存放的, 低位数据放在低地址上, 不符合人类的阅读顺序, 比如: 对int类型数据 它的数据是: b3 b2 b1 b0, 内存从低到高: b0 b1 b2 b3
结构体对齐
-
第一个成员的偏移量为0
-
其它成员的偏移量是其对齐数的整数倍
-
结构体的大小为最大对齐数的整数倍
例子: struct One { char a; double b; short c; int d; char e; }; 规则1: a的偏移量是0 规则2: 由于b的对齐数是8,所以1个字节补7个字节,b的偏移量为 0 + 1 + 7 = 8 由于c的对齐数是2,c前面的长度是 8 + 8 = 16, 是2的倍数, 所以c的偏移量是 16 由于d的对齐数是4,d前面的长度是 16 + 2 = 18, 不是4的倍数, 补2个字节, d的偏移量是 18 + 2 = 20 由于e的对齐数是1,e前面的长度是 20 + 4 = 24, 是1的倍数, d的偏移量就是 24 整体的大小为 24 + 1 = 25 规则3: 由于25不是最大对齐数的整数倍, 所以补7个字节, 为32
另外:
如果使用了 #pragma pack(4) 这个宏可以改变最大对齐数, 这意味着比如double的对齐数就是4. 也可以使用: __attribute__((__aligned__(4))) #pragma pack(4) struct One { char a; double b; short c; int d; char e; }; 规则1: a的偏移量是0 规则2: 由于b的对齐数是4,所以1个字节补3个字节,b的偏移量为 0 + 1 + 3 = 4 由于c的对齐数是2,c前面的长度是 4 + 8 = 12, 是2的倍数, 所以c的偏移量是 12 由于d的对齐数是4,d前面的长度是 12 + 2 = 14, 不是4的倍数, 补2个字节, d的偏移量是 14 + 2 = 16 由于e的对齐数是1,e前面的长度是 16 + 4 = 20, 是1的倍数, d的偏移量就是 20 整体的大小为 20 + 1 = 21 规则3: 由于21不是最大对齐数的整数倍, 所以补3个字节, 为24
gcc中常用属性
用于设置编译器的一些特殊行为
设置对齐字节数:
__attribute__((__aligned__(4)))
取消优化对齐, 按照实际字节数存储
__attribute__((packed))
将函数或数据放到特定的代码段:
__attribute__((section("section-name")))
阻止函数内联:
__attribute__((noinline))
让函数总是内联:
__attribute__((__always_inline__))
设置特定函数的优化级别, O0,O1,O2,O3:
__attribute__((optmize("Ox")))
gcc 优化
优化级别
O0,O1,O2,O3,Os
优化方法
通过给gcc参数:
gcc -O2 ...
通过代码:
给这行代码以下的代码设置优化级别:
#pragma GCC optimize ("O3")
给特定函数设置属性:
__attribute__((optmize("O3")))
main函数之前发生的事
大概是:
- 设置栈帧
- 设置bss区域数据为0
- 如果需要, 执行 hardware/software init
- 配置参数
- 调用main
- 执行exit.