C++中的栈和堆

由C/C++编译的程序占用的内存分为以下几个部分:

 1、栈区(stack):又编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构的栈。
 2、堆区(heap):一般是由程序员分配释放,若程序员不释放的话,程序结束时可能由OS回收,值得注意的是他与数据结构的堆是两回事,分配方式倒是类似于数据结构的链表。
 3、全局区(static):也叫静态数据内存空间,存储全局变量和静态变量,全局变量和静态变量的存储是放一块的,初始化的全局变量和静态变量放一块区域,没有初始化的在相邻的另一块区域,程序结束后由系统释放。
 4、文字常量区:常量字符串就是放在这里,程序结束后由系统释放。
 5、程序代码区:存放函数体的二进制代码。

一、栈空间

1.1 自动释放内存无需码农操作

  在程序中定义的局部变量(包括了指针类型变量)、局部数组等都是存储在栈空间中。栈空间具有一个鲜明的特点:函数内定义的变量出了函数范围,其所占用的内存空间自动释放。但是,栈空间的尺寸有最大限制,不适合分配大空间使用

  所以,因为栈空间出了函数范围就释放,所以不适合要给其他地方使用的内存需求。其最大的好处就在于:不用程序员手动释放内存。

参考我们上一篇文章给出的例子:

CTest* fun(CBest* pBest)
{
   CTest* pTest = new CTest();//局部变量,虽然使用了new,但是我们不用手动释放(出了这个函数也没地方释放啊)
   pTest->a = pBest->b;
   return pTest;
}

1.2 不要把局部变量的指针做为返回值返回

  首先,我们来看看下面一段代码,其中getData函数返回了一个int数组类型的指针,而getData2函数返回了另一个int数组类型的指针:

int *getData()
{
    int nums[10]={1,2,3,4,5,6,7,8};//在栈中
    return nums;
}
 
int *getData2()
{
    int aaa[10]={8,7,6,5,4,3,2,1};//在栈中
    return aaa;
}

int main(int argc, char *argv[])
{
    int *nums = getData();
    printf("%d,%d,%d\n",nums[0],nums[1],nums[2]);//可以正常返回
    return 0;
}

我们修改一下main函数:

int main(int argc, char *argv[])
{
    int *nums = getData();
    getData2();
    printf("%d,%d,%d\n",nums[0],nums[1],nums[2]);

    return 0;
}

上述测试在某些系统是不能正常执行的(例如在win7+codeblocks下,可以编译通过(编译warning getData()返回局部变量:address of local variable 'aaa' returned ),但执行报错)

这显然是不正常的!为什么呢?

这是因为栈是由系统自动分配和释放(局部数组是分配在栈中的,出了函数,局部数组就会被释放掉),函数内部的局部变量的生命周期就只是在函数周期内,只要函数执行完毕,那么其内部的局部变量的生命周期也就结束了。于是,当我们执行完第一句代码后,nums指针所指向的数组的那一块内存区域(getData()中的指针变量nums指向的长度为10的区域)可能就已经被释放了,但是数据还未清理也就是还留在那儿。但是,当我们执行完第二句代码后,在getData2函数中又定义了一个数组aaa,它又将刚刚释放的栈空间内存占用了,于是main中的nums所指向的这块区域(main中的nums指针是正常的,它还是有值的,并且值没有发生变化,还是指向某个位置,但是其指向的位置的内容可能已经变了,原来指向getData()中的nums数组所在的位置,由于getData()执行完后,nums数组释放。后面调用getData2(),里面申请了aaa数组,这个aaa就可能占用了原来getData()中的nums数组对应的区域,然后就悲剧了)就是aaa了。当执行完第二句代码,aaa又被释放了,但是其数据还在那里并未清除,也就是我们前面几篇提到的脏内存区域。所以,最后显示的就是8,7,6而不是1,2,3了。 

二、堆空间

2.1 用户掌握堆的分配

栈空间最大的优点就是栈空间出了函数范围就释放,不需要程序员手动释放。但是,如果我们想自己控制内存的分配呢?这时候,就可以使用堆空间来存储,堆空间可以存储栈空间无法存储的大内存。这里,我们可以借助malloc函数在堆空间中分配一块指定大小的内存,用完之后,调用free函数及时释放内存。

  // malloc(要分配的字节数)
    int *nums = (int*)malloc(sizeof(int)*10);
    nums[0]=1;
    nums[1]=8;
    free(nums);

  需要注意的是:在malloc函数中需要指定要分配的内存所占用的字节大小。当然也可以用new和delete来实现内存的申请和释放。

2.2 函数返回指针的几种解决办法

  (1)在方法内malloc,用完了由调用者free

  这里我们可以结合malloc和free来解决我们在栈空间中所遇到的问题,重写上面的代码如下:

int *getData()
{
    int *nums = (int*)malloc(sizeof(int)*10);//在堆中申请10个int长度的空间
    nums[0]=1;
    nums[1]=8;
    nums[2]=3;

    return nums;
}

int *getData2()
{
    int *nums = (int*)malloc(sizeof(int)*10);//在堆中申请10个int长度的空间
    nums[0]=2;
    nums[1]=7;
    nums[2]=5;

    return nums;
}

int main(int argc, char *argv[])
{
    int *numsptr = getData();
    int *numsptr2 = getData2();或者getData2();
    // numptr[1]等价于*(numptr+1)
    printf("%d,%d,%d\n",numsptr[0],numsptr[1],numsptr[2]);
    // 不要忘记释放内存
    free(numsptr);
    free(numsptr2);

    return 0;
}

这次可以正常执行,返回的都是1,2,3.输出的还是getData函数返回的指针所指向的内存区域的数据,没有出现交叉影响,完美!

(2)把局部变量定义为static

char *getStr()
{
    static char strs[]="afsafdasfdsdfsaddafafafasdfadfs";
    return strs;
}

int main(int argc, char *argv[])
{
    char* strsptr = getStr();

    return 0;
}

strs存放在全局区,它直到程序结束后才会释放。But,需要注意的是:不适合于多线程调用,如果想保存返回内容,你需要调用者尽快复制一份。

(3)(推荐)由调用者分配内存空间,只是把指针发给函数,函数内部把数据拷贝到内存中

  这里怎么来理解呢,也就是三个步骤,第一步:由调用者分配内存空间;第二步:把指针传递给函数;第三步:函数内部把数据拷贝到内存中。下面我们通过一个小案例:从文件名分析文件名和扩展名,来看看这三个步骤怎么来实现。

// Step3:函数内部把数据拷贝到内存中
void parseFileName(char* filename,char* name,char* ext)
{
    char *ptr = filename;
    while(*ptr != '\0')
    {
        ptr++;
    }
    // 记录结尾的指针
    char *endPtr = ptr;

    //ptr移动到了字符串的结尾,再把ptr移动到"."的位置
    while(*ptr != '.')
    {
        ptr--;
    }

    // 两个指针相减表示两个指针相隔的元素的个数
    memcpy(name,filename,(ptr-filename)*sizeof(char));
    memcpy(ext,ptr+1,(endPtr-ptr)*sizeof(char));
}

int main(int argc, char *argv[])
{
    // Step1:由调用者分配内存空间
    char str[] = "[TK-300]美.女.avi"; 
    char name[20] = {0};
    char ext[20] = {0};
    // Step2:只是把指针传递给函数
    parseFileName(str,name,ext);
    printf("解析完成:\n");
    printf("文件名:%s,后缀:%s\n",name,ext);
    
    return 0;
}

这种方法避免了函数返回指针,程序员手动分配的内存都是在栈空间中,然后函数内部处理后再将经过逻辑处理后的数据存储到栈空间中的指定区域内,最后main函数中再访问修改后的内存区域。这里的运行结果如下图所示:

最后注意:

1. new出来的空间,要用delete释放掉;

2. 释放的是申请到的内存区域 ,而不是指针;

3.释放掉的是指针指向的那个区域的内容,而不是释放掉指针本身;

4.如果不做指针置为NULL的操作,则指针还指向原来的内存位置,如果该位置的内容发生变化,则再用指针,可能会出错。

参考:

http://www.cnblogs.com/edisonchou/p/4669098.html 

如鹏网,《C语言也能干大事(第三版)》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值