最近因为函数中访问数组越界导致段错误,于是对函数栈进行了分析,得出一些结论:
1, 函数局部变量栈地址是运行时才分配的,函数调用返回后分配的站空间被回收重复利用
2, 程序中所有函数共用一段栈空间,可以把它称为线程栈空间——线程栈起始地址及栈大小,被调的函数运行过程不断在这段栈空间进行进栈出栈,某段地址可能会被重复使用多次。
3, 局部变量数组访问越界并不一定会导致段错误,越界访问肯定产生内存栈区内容覆盖,覆盖的是关键数据时会导致异常,比如函数返回地址被覆盖,此时pc指针指向了未知区域而导致段错误。
4, 函数栈分配原则先给本函数分配栈区,运行到调用的子函数时再给子函数分配栈区,栈区分配起始地址紧接上次分配的最后地址。
5, 如果函数中有条件语句,例如:
If(ret == 0)
{
S8 a[100] = {0};
}
Else
{
S8 b[200] = {0};
}
则会给此函数分配条件语句中占空间最大的一段区间,即分配200个字节,如果ret是0,则a会占用200个字节栈区空间,如果将s8 a[100] = {0}; 这条语句去掉,还会占用200字节栈区空间。
现对于上面所述进行部分验证:
代码:
void get_info(s8 *info)
{
s8 *msg = "hello";
memcpy(info, msg, strlen(msg));
printf("@[%s] msg addr:%u\n", __FUNCTION__, (u32)&msg);
}
void indirect_call(void)
{
s8 a[8] = {0};
get_info(a);
printf("@[%s] a addr:%u a:%s\n", __FUNCTION__, (u32)a, a);
}
int main(void)
{
s8 a[8] = {0};
get_info(a);
printf("@[%s] a addr:%u a:%s\n", __FUNCTION__, (u32)a, a);
indirect_call();
s8 b[10] = {0};
printf("@[%s] b addr:%u\n", __FUNCTION__, (u32)b);
return 0;
}
描述:直接调用get_info与间接调用get_info函数查看局部变量地址变化情况。
运行:
@[get_info] msg addr:3215719804
@[main] a addr:3215719860 a:hello
@[get_info] msg addr:3215719756
@[indirect_call] a addr:3215719796 a:hello
@[main] b addr:3215719850
从运行的结果可以看出,进入main函数时,局部变量a,b就被分配了栈空间,通过访问局部变量通过栈指针、函数栈起始地址及偏移量实现的,这个可以反汇编得出结论,在此省略。程序运行到调用get_info函数时,又产生了一次栈空间分配,内存分布如下:
栈地址增长方向由高到低的,数组a地址为3215719860,占用8个字节,接着是数组b的地址3215719850,刚好偏移10个字节是数组b的大小,中间一段空白区是保存调用get_info函数时相关信息,比如状态寄存器,返回地址,临时变量等。接着是get_info函数中指针变量msg地址,调用get_info函数完成后3215719804—3215719850这段栈空间被释放。
程序运行到调用indirect_call函数时,站空间又从起始地址3215719850分配一段地址给indirect_call函数,我们可以看到此函数里面数组a的地址为3215719796,占用8个字节一直到321579804,接着又调用了一次get_info函数,此时指针msg地址变为了321579756,栈地址分布如下:
至此,我们可以得到两个信息:1,函数栈空间在运行时分配的,两次调用get_info,指针变量msg地址不一样;2,321719850往下栈地址被分配使用回收后又再一次被分配使用;3,数组b的地址比msg地址大,说明b的地址分配先于调用get_info函数,虽然b的定义在调用get_info函数之后。