函数执行时有自己的临时栈空间,c++成员函数有两个临时栈空间,一个是成员函数的还有一个是对象的。
函数的参数是压进临时栈中,传递的实参用来初始化临时栈中的形参。
函数属性:
int __attribute__((stdcall)) add(int a, int b)
{
return a+b;
}
一共有3种属性(调用方式):stdcall,cdecl,fastcall,他们会影响编译:
函数栈压栈的参数顺序(这三个都是从右到左)
函数栈清空方式
函数的名字转换
前两点可反汇编看看:
gcc XXX.c -S
cat XXX.s
第三点可以:
nm 程序
可以看到c程序 add 函数名字就叫add c++因为有重载机制会 把名字 变成 列如 Z3addPis Z 是都有的,3表示函数名字的长度,后面的是函数形参类型——C++的重载其实是一个假象!编译之后函数的符号是不一样的。
#include<stdio.h>
#include<malloc.h>
int main()
{
int *p = malloc(4);
printf("%p\n",p);
while(1);
return 0;
}
结果:0xb7786018
#include<stdio.h>
#include<malloc.h>
int main()
{
int *p = (int*)0xb7786018;
printf("%d\n",*p);
return 0;
}
可能会出现段错误,或随机的值。
#include<stdio.h>
#include<malloc.h>
int main()
{
int *p = malloc(0);
*p = 999;
printf("%d\n",*p);
return 0;
}
输出:999
问题:一个程序不能访问另外一个程序的地址指向的空间。
理解:
1.每个程序的开始地址是相同的
2.程序中使用的地址不是物理地址,而是逻辑地址。
(逻辑地址仅仅是个编号,它使用int 4字节整数表示 (4G),每个程序提供了4G的访问能力。)
逻辑地址与物理地址之间存在映射,这个过程叫做内存映射。
strace 程序
#跟踪程序的执行过程
虚拟内存禁止了用户直接访问物理存储设备, 提高了系统的稳定性。
对于第一个程序,a分配了空间,即有了内存映射了,所以正确执行。
对于第二个程序,p指向了地址0xb7786018,而这个地址在本程序中并没有映射到物理内存中,所以访问会出错。
#include<stdio.h>
#include<malloc.h>
int main()
{
int *p = malloc(4);
*(p) = 1;
*(p+1) = 2;
*(p+1000) = 3;
printf("%p\n",p);
while(1);
return 0;
}
上面程序执行并不会发生段错误。
原因:内存映射的基本单位是 4k (即1000,16进制,叫内存页)。哪怕我们要1个字节,它映射的也是4k空间,malloc 第一次被调用的时候 不管你分不分配空间,它至少调用4k的空间。(所以上面的代码以及第三个代码不会出现段错误)
段错误:地址没有映射到一个物理空间,无效访问
合法访问:比如malloc分配的多余空间可以访问,但属于非法访问!
栈:编译器自动生成代码维护
堆:地址是否映射,映射的空间是否被管理
brk / sbrk 内存映射函数
man brk
#查看说明文档
int brk(void *end_data_segment);
分配空间,释放空间
void *sbrk(intptr_t increment);
返回空间地址
应用:
1.使用sbrk分配空间
2.使用sbrk得到没有映射的虚拟地址(参数为0)
3.使用brk分配空间
4.使用brk释放空间
sbrk与brk后台系统维护一个指针。
指针初始化为null。
调用sbrk时,判断指针是否为null,如果是,得到大块空闲空间的首地址来初始化指针,同时把指针+increment;否则,先返回当前指针,并且把指针位置+increment;
在映射的时候,都是映射一个页,以此来提高效率。
#include <stdio.h>
#include <unistd.h>
main()
{
int *p = sbrk(4);//得到大块空闲空间的首地址来初始化指针并返回,然后把维护的指针+4
*p = 8888;
printf("p:%p\n", p);
printf("*p:%d\n", *p);
}
结果:
p:0x93c3000
*p:8888
一切正常。
我们看看参数为0的情况:
#include <stdio.h>
#include <unistd.h>
main()
{
int *p = sbrk(0);//初次调用,且参数为0,获得没有映射的虚拟地址
*p = 8888;
printf("p:%p\n", p);
printf("*p:%d\n", *p);
}
结果:段错误 (核心已转储)
我们加一个死循环,进入/proc/${pid}/maps 看看:
#include <stdio.h>
#include <unistd.h>
main()
{
int *p = sbrk(0);//获得没有映射的虚拟地址
printf("p:%p\n", p);
printf("pid:%d\n", getpid());
while(1);
}
结果:
p:0x9482000
pid:2933
但是我们进入/proc/2933/maps中查看之后,并找不到p所指向的内存空间地址!说明该虚拟地址并没有映射到物理地址上,所以上一个程序当然会出现段错误了。
我们再看看下面一个例子:
#include <stdio.h>
#include <unistd.h>
main()
{
int *p = sbrk(0);//获得没有映射的虚拟地址,得到大块空闲空间的首地址来初始化指针,同时把指针+increment
brk(p+1);//分配空间,映射
*p = 8888;
}
该程序执行没有出错,首先,sbrk(0)返回了一个虚拟地址,但是还没映射。之后调用brk,brk检测到p+1还未映射,于是分配空间,进行映射。
brk(p);//释放空间 *p = 8888;
如果我们加上以上代码,又会出现段错误,因为空间被释放了!
sbrk:表示指针的相对移动;
brk:表示指针的绝对移动,如发现当前指针地址未映射,则进行映射。
为说明sbrk的相对移动以及空间的分配与释放,我们看下面的例子:
#include <stdio.h>
#include <unistd.h>
main()
{
int *p1 = sbrk(4);
int *p2 = sbrk(200);
int *p3 = sbrk(400);
int *p4 = sbrk(-4);
int *p5 = sbrk(-4);
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p3);
printf("%p\n", p4);
printf("%p\n", p5);
}
结果:
//我们假设sbrk维护的指针叫做q
0x88b4000 //分配一个页的地址q并返回给p1,q = q+4
0x88b4004 //取得q,q = q+200
0x88b40cc //取得q,q = q+400
0x88b425c //取得q,q = q-4
0x88b4258 //取得q,q = q-4
我们可以看出,sbrk都是相对指针的当前位置进行移动的~通过参数的正负,可以实现空间的分配与释放。
如果成功,brk返回0,sbrk返回指针;
如果失败,brk返回-1,sbrk返回(void *)-1。
检测错误: 使用 perror(“err:”);函数。