堆,栈,函数,alloca

来自网络资料和我的理解,如有错误,敬请指正!

alloca() 是什么?为什么不提倡使用它?


在调用 alloca() 的函数返回的时候, 它分配的内存会自动释放。也就是说, 用 alloca 分配的内存在某种程度上局部于函数的 ``堆栈帧"  或上下文中。
alloca() 不具可移植性, 而且在没有传统堆栈的机器上很难实现。 当它的返回值直接传入另一个函数时会带来问题, 如  fgets(alloca(100), 100, stdin)。
由于这些原因, alloca() 不合标准, 不宜使用在必须广泛移植的程序中, 不管它可能多么有用。 既然 C99 支持变长数组(VLA), 它可以用来更好的 完成 alloca() 以前的任务。

堆 malloc calloc realloc,栈 alloca(函数栈)

堆栈与函数调用

1) 在栈上创建。在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,一般使用寄存器来存取,效率很高,但是分配的内存容量有限。
2) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete来释放内存。动态内存的生存期由程序员自己决定,使用非常灵活。 
3) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 
4) 文字常量分配在文字常量区,程序结束后由系统释放。 
5) 程序代码区。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int a = 0;//全局初始化区 
char *p1; //全局未初始化区 
void main() 
{ 
    int b;//栈 
    char s[]="abc";   //栈 
    char *p2;         //栈 
    char *p3="123456";//123456\0在常量区,p3在栈上。 
    static int c=0;   //全局(静态)初始化区 
    p1 = (char*)malloc(10); 
    p2 = (char*)malloc(20);//分配得来得10和20字节的区域就在堆区。 
    strcpy(p1,"123456");   //123456\0放在常量区,编译器可能会将它与p3所向"123456\0"优化成一个地方。 
}

栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。 


堆对象创建和销毁都要由程序员负责,所以,如果处理不好,就会发生内存问题。如果分配了堆对象,却忘记了释放,就会产生内存泄漏;而如 果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时就导致程序崩溃。但是高效的使用堆对象也可以大大的提高代码质量。比如,我们需要创建一个大对象,且需要被多个函数所访问,那么这个时候创建一个堆对象无疑是良好的选择,因为我们通过在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享,相比整个对象的传递,大大的降低了对象的拷贝时间。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。


静态存储区。所有的静态对象、全局对象都于静态存储区分配。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代 码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之 前,会调用由编译器生成的exit函数,来释放所有的全局对象。比如下面的代码:


void main(void) 

//显式代码 
}


实际上,被转化成这样:
void main(void) 

_main();//隐式代码,由编译器产生,用以构造所有全局对象


//显式代码


exit();// 隐式代码,由编译器产生,用以释放所有全局对象 
}


除了全局静态对象,还有局部静态对象通和class的静态成员,局部静态对象是在函数中定义的,就像栈对象一样,只不过,其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时,才销毁该对象。class的静态成员的生命周期是该class的第一次调用到程序的结束。


编译器一般使用栈来存放函数的参数,局部变量等来实现函数调用。有时候函数有嵌套调用,这个时候栈中会有多个函数的信息,每个函数占用一个连续的区域。一个函数占用的区域被称作帧。同时栈是线程独立的,每个线程都有自己的栈。


通过命令查询系统限制的应用程序栈最大长度

# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 28905
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 10000
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

其中stack size说明应用程序的栈最大值

下面一段程序模拟栈溢出时,程序发生段错误的情况:

当 #define xTEST_ALLOCA 使用可变长数组

当 #define TEST_ALLOCA 使用alloca函数在栈上分配数据


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define xTEST_ALLOCA
void test_stack_vla(size_t len)
{
    char s[len];
    memset(s, 0, len);
}
void test_stack(size_t len)
{
    size_t i = 0;
    size_t s = 0;
    while(i++ < 1024 * 2)
    {
        s += len;
        printf("i = %lu, s = %lu\r\n", i, s);
#ifdef TEST_ALLOCA
        char *p = alloca(len);
        memset(p, 0, len);
#else
        test_stack_vla(s);
#endif
    }
}
int main(int argc, char *argv[])
{
    printf("%d, %s\r\n", argc, argv[0]);
    //100K 栈内容
    char sStackSpace[1024 * 10 * 10] = { 0 };
    printf("%s\r\n", sStackSpace);
    //逐渐增加栈大小,每次增加10K
    test_stack(1024 * 10);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值