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

结构体对齐

  1. 第一个成员的偏移量为0

  2. 其它成员的偏移量是其对齐数的整数倍

  3. 结构体的大小为最大对齐数的整数倍

    例子: 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函数之前发生的事

大概是:

  1. 设置栈帧
  2. 设置bss区域数据为0
  3. 如果需要, 执行 hardware/software init
  4. 配置参数
  5. 调用main
  6. 执行exit.