指针基础-栈空间与堆空间

本文详细介绍了C/C++程序中栈和堆的内存管理,包括它们的区别和使用场景。栈空间由系统自动分配和释放,适合小规模内存,但有大小限制;堆空间需要程序员手动管理,适用于大内存分配。栈空间中局部变量的指针作为返回值可能导致内存问题,而堆空间可通过malloc/free进行动态内存分配。文章还提出了函数返回指针的三种解决方案,并讨论了堆在ROM或RAM中的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、指针基础-栈空间与堆空间

在这里插入图片描述
一个由C/C++编译的程序占用的内存分为以下几个部分:

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

一、栈空间

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

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

linux系统可以通过ulimite -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) 27128
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 27128
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

其中-s 是栈区的大小限制。

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

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;
}

我们在main函数中首先调用getData函数,看看能否取得nums数组的前三个元素:

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

运行结果如下图所示,我们发现是OK的,原来可以将局部变量的指针作为返回值返回呢!
在这里插入图片描述
但是,如果我们在调用getData函数之后,又调用了getData2函数呢,这时还能正确地打印nums数组吗?看看下面的执行顺序:

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

    return 0;
}

在打印之前,我们又执行了getData2函数,那么运行结果呢,看看下图吧:
在这里插入图片描述
那么,问题来了,这是为什么呢?我们刚刚提到了,栈是由系统自动分配和释放,函数内部的局部变量的生命周期就只是在函数周期内,只要函数执行完毕,那么其内部的局部变量的生命周期也就结束了。于是,当我们执行完第一句代码后,nums指针所指向的数组的那一块内存区域可能就已经被释放了,但是数据还未清理也就是还留在那儿。但是,当我们执行完第二句代码后,在getData2函数中又定义了一个数组aaa,它又将刚刚释放的栈空间内存占用了,于是nums所指向的这块区域就是aaa了。当执行完第二句代码,aaa又被释放了,但是其数据还在那里并未清除,也就是我们前面几篇提到的脏内存区域。所以,最后显示的就是8,7,6而不是1,2,3了。

二、堆空间

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

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

需要注意的是:在malloc函数中需要指定要分配的内存所占用的字节大小。
  
2.2 函数返回指针的几种解决办法

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

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

 int *getData()
{
    int *nums = (int*)malloc(sizeof(int)*10);
    nums[0]=1;
    nums[1]=8;
    nums[2]=3;

    return nums;
}

int *getData2()
{
    int *nums = (int*)malloc(sizeof(int)*10);
    nums[0]=2;
    nums[1]=7;
    nums[2]=5;

    return nums;
}

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

    return 0;
}

这里我们将所有要返回的指针都改为了使用malloc动态分配的,在main函数中调用free将内存手动释放掉,来看看运行结果:
在这里插入图片描述
 这下输出的还是getData函数返回的指针所指向的内存区域的数据,没有出现交叉影响,完美!
 
 (2)把局部变量定义为static

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

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

    return 0;
}

由本文开篇可知,除了栈空间和堆空间,还有一块全局区,它直到程序结束后才会释放。

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函数中再访问修改后的内存区域。这里的运行结果如下图所示:
在这里插入图片描述
虽然这个文件名有点邪恶,但是功能还是完成了,哎哟,不错哦!

三:堆在ROM还是RAM?

程序开始启动

当系统上电后根据BOOT的引导配置选择启动方式,默认是Flash启动,这时系统开始把所有的代码段搬到RAM中去运行
CPU就从内存中(RAM)获取数据和指令,根据相关指令来控制系统运行

数据存放位置:除了特定IO操作存到EEPROM里面,其他变量的使用全在RAM区,其中就有堆和栈。堆是由用户手动分配malloc,在整个程序运行期间都有效除非手动释放free。而栈在一个函数体内存在,例如main方法由系统自动创建,如果出现递归创建则很容易导致栈溢出,当系统执行完该函数功能后,栈空间数据也由系统自动销毁。

在这里插入图片描述

<think>嗯,用户问的是智能指针分配内存空间是在还是。我需要先回顾一下智能指针的基本概念。智能指针在C++中是用来管理动态分配的内存的,对吧?比如unique_ptr、shared_ptr这些。那动态分配的内存通常是在上的,所以可能智能指针本身是在上,而它指向的对象是在上。 不过用户可能混淆了指针本身和所指向的内存的位置。比如,智能指针作为一个对象,它的实例是分配在上的,但是它会管理一个上的内存。比如,当用make_shared或者new创建对象时,内存是在上分配的,而智能指针本身作为对象,生命周期结束时自动释放内存。 那用户可能想知道的是智能指针如何管理内存的位置。我需要分清楚智能指针对象的存储位置和它们管理的资源的存储位置。例如,shared_ptr的控制块可能也是在上,但这是内部实现细节,用户可能不需要太深入,但需要明确说明两者的区别。 另外,用户可能对的区别不太清楚,可能需要简单解释一下的基本概念。是自动分配释放的,而需要手动管理,但智能指针帮助自动管理内存,防止泄漏。 可能的误区是认为智能指针本身在上,但其实智能指针变量通常是在上的,除非用new创建智能指针实例,但这种情况很少见。大部分情况下,用户使用智能指针都是在上创建对象,然后管理内存。 所以回答的结构应该是先介绍智能指针是什么,然后说明它们如何管理内存,同时自身存储在上。再举例说明几种智能指针的区别,比如unique_ptr和shared_ptr,以及控制块的位置。最后总结智能指针的作用和的区别。</think>智能指针是 C++ 中用于管理动态内存(内存)的工具,其核心目标是**自动管理内存的释放**,避免内存泄漏。以下是内存分配相关的关键点: --- ### 1. **内存分配的位置** - **智能指针对象本身**: 智能指针(如 `std::unique_ptr`、`std::shared_ptr`)是一个对象(Stack),其生命周期由作用域控制,会自动调用析构函数。 - **智能指针管理的资源**: 它指向的内存(通过 `new` 或 `std::make_shared`/`std::make_unique` 分配)一定位于**(Heap)**。智能指针通过析构函数自动释放这部分内存。 --- ### 2. **(Heap) vs (Stack)** - **内存**: 由编译器自动分配/释放,用于局部变量。内存大小固定且有限,不适合大型数据。 - **内存**: 手动分配(通过 `new`/`malloc`)和释放(通过 `delete`/`free`),容量更大但需主动管理。智能指针的核心作用就是自动化这一过程。 --- ### 3. **智能指针的工作机制** - **示例:`std::unique_ptr`** ```cpp { // 分配内存,ptr 本身在上 std::unique_ptr<int> ptr = std::make_unique<int>(42); // 使用 ptr... } // 离开作用域时,ptr 自动释放内存 ``` - `ptr` 是对象,析构时调用 `delete` 释放其管理的内存。 - **示例:`std::shared_ptr`** ```cpp { // 控制块和对象可能一起分配在上(取决于实现) std::shared_ptr<int> ptr = std::make_shared<int>(42); } // 引用计数归零时,内存被释放 ``` - `std::shared_ptr` 可能将对象和控制块(存储引用计数)分配在连续内存中,优化性能。 --- ### 4. **智能指针的核心价值** - **所有权管理**:明确内存的所有权(如 `unique_ptr` 独占,`shared_ptr` 共享)。 - **异常安全**:即使发生异常,智能指针仍能保证内存释放。 - **替代裸指针**:避免手动 `new`/`delete`,减少内存泄漏风险。 --- ### 总结 - 智能指针本身是对象,管理的是内存。 - 内存的自动释放是其核心功能,解决了手动管理的痛点。 - 选择 `unique_ptr`(独占所有权)或 `shared_ptr`(共享所有权)取决于场景需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值