深入理解C语言-07 函数

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上下文。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值