C语言的基本设计层次结构如下:
1> 以{} 封装的代码单元块
2> 函数
3> 模块
函数是C语言设计中最核心最基本的构成组件。
函数设计中主要考虑以下几点:
1> 是否可重入 参考http://blog.csdn.net/yueyahe/article/details/729126
2> 是否为对外接口
3> 是否为同步函数
4> 参数是输入还是输出,或者输入输出均是?
5> 函数的功能
这些信息务必在函数说明中详细标注。
项目实践中,有太多搞出函数设计意图而导致的严重bug。
接下来,从编译器角度探讨函数的具体实现。
C语言采用的是Stack模型。
编译好的程序运行时主要依赖以下几个区域:
1> text
2> data(初始化过的全局数据)
3> bss(未初始化的数据)
4> stack(函数工作区)
5> heap (动态内存管理区)
不同的系统,1-5的分配区域可能会有差别。
可以写一个测试代码来了解其分配区域。
/*
* 了解内存布局
*/
int gInt = 0x11111111;
int gIntUninit;
char* pStr = "GlobalString";
int testMem(void)
{
int lInt = 0x33333333;
char* pLStr = "LocalString";
char* pHeap = (char*)malloc(100);
gIntUninit = 0x22222222;
printf("Global gInt‘ address is 0x%x\n", &gInt);
printf("Global gIntUninit‘ address is 0x%x\n", &gIntUninit);
printf("Local lInt‘ address is 0x%x\n", &lInt);
printf("Function testMem‘ address is 0x%x\n", testMem);
printf("Global Const string‘ address is 0x%x\n", pStr);
printf("Local Const string‘ address is 0x%x\n", pLStr);
printf("HEAP pHeap‘ address is 0x%x\n", pHeap);
free(pHeap);
return 0;
}
输出结果如下:
Global gInt‘ address is 0x418038
Global gIntUninit‘ address is 0x418164
Local lInt‘ address is 0x12fe8c
Function testMem‘ address is 0x41120d
Global Const string‘ address is 0x41583c
Local Const string‘ address is 0x415abc
HEAP pHeap‘ address is 0x393810
了解特定平台内存分布的主要意义是方便我们调试。
比如一个指针错误,可以根据地址值大致推测其变量的分布区域。
与函数运行最相关的是Stack区域。
考虑Stack分布前,先考虑函数的上下文。
从设计的角度来说,函数的上下文是参数 + 返回值,调用者。
从编译器角度来说,每个函数均有一个活动记录(Activation Record)或者叫Frame。
1> 参数
2> 返回点
3> 寄存器
4> 内部变量
5> 返回值
考虑最简单的一种情况。
main函数 -> 调用 函数1 -> 函数2
则Stack的内存分布为:
高地址:
main函数的活动记录
函数1的活动记录
函数2的活动记录
...
备注: 一般Stack采用高地址为基地址,从高到低往下增长。
关于参数的入栈顺序主要有以下几种:
_cdecl 从右到左 调用者负责将参数弹出栈。
_stdcall 从右至左 被调用者负责将参数弹出栈。
__fastcall 采用寄存器传递参数。
VC下使用方式: 在函数名字前加 _cdecl等标志。
gcc下使用方式 __attribute__((stdcall))
寄存器的约定:
x86-32下一般按以下约定:
eax edx ecx 调用者负责恢复 即函数A调用函数B之前,需要将其push到Stack。
ebx esi edi 被调用者负责恢复 即函数A调用函数B时,函数B需要先将其push到Stack,再使用,返回函数B前pop出Stack。
内部变量:
当CPU没有足够多的寄存器来存储所有临时变量时,需要动态将临时变量存储到寄存器。
C语言与这两个有关系的关键字是: register 与 volatile
register : 建议编译器使用寄存器
volatile : 阻止编译器对其进行优化,一般对硬件端口的操作需要使用该关键字。
返回值:
x86下默认是保存在eax
总而言之,在系统异常时,需要分析函数的调用Stack上下文。