C语言:内存存储、栈、堆等区别

本文详细解析了C/C++的内存存储区域,包括栈、堆、全局/静态存储区和常量存储区的特点及应用。并通过具体示例介绍了函数参数传递机制、局部变量与全局变量的作用范围以及内存泄漏等问题。

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

内存存储

首先先了解一下C/C++的内存存储,一般把内存理解成4个分区:栈、堆、全局/静态存储区和常量存储区。

1)栈:用于那些在编译期间就能确定存储大小的变量的存储区,用于在函数作用域内创建,在离开作用域后自动销毁的变量的存储区。它的存储空间是连续的,两个紧挨着定义的局部变量,它们的存储空间也是紧挨着的。一般栈的大小有限制,所以定义超大数组时需注意,如vc++编译器的默认栈大小为1M。

2)堆:哪些在编译期间不能确定存储大小的变量的存储区,它的存储空间是不连续的,一般由malloc(new)函数来分配内存块,并且需free(delete)释放内存。如没释放便会造成内存泄漏问题。两个紧挨着定义的指针变量,所指向的分配出来的两块内存并不一定是紧挨着的。堆的大小由虚拟内存而定,32位机器的大小为2的32次方,即为4GB。
3)全局/静态存储区:和“栈”一样,用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。
4)常量存储区:用于那些在编译期间就能确定存储大小的变量的存储区,并且在程序运行期间,存储区的常量也是全局可见的,而存放的是常量,不允许被修改。

了解了c/c++内存的存储情况后,以以下5个问题来做分析。
问题1:

int x = 4,y = 3;
swap(x,y);
int z = x - y;
printf("result = %d\n",z);//output 1
void swap(int a, int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

初学者都知道:a 和 b没有发生交换。
这里用函数参数和内存的关系做解析:
所有函数都会在运行时从程序“栈”上得到分配给它的一块存储区,这块栈上的函数存储区随函数的开始而开始,随函数的结束而结束,结束时便将存储区自动释放,以供程序的其他用途使用。函数在函数运行时会为函数的每一个参数都提供存储区,参数在存储区中的存储长度由自身的类型决定。
函数的参数有传值(传指针)和传址(传引用)两种。所谓参数传值,就是把实参的值复制到函数运行时分配给函数的参数存储区中,即函数不会访问当前调用的实参,而处理的只是实参在本地的拷贝,而对这些拷贝的修改便不会影响实参的值。
那么我们所定义的swap便是参数传值的函数,这就意味着函数体内的a和b是参数int a和int b的实参在函数内的一份局部拷贝。实际上也就是这两份拷贝进行了交换,而并没对实参进行修改。而当函数结束时,函数的“栈”存储区自动释放,局部变量a和b也随即被销毁。
问题2:

int getint(int a)
{
    int i = 1 + a;
    return i;
}

int z = 1;
z = getint(a);

z的结果为2,想必也不会答错吧。疑问点在于,很多人经常会这样问:函数getint返回的i是个局部变量,函数结束时不时被自动销毁了么,怎么还能返回给调用者?
同样的,函数的返回值有传值和传址两种。getint属于返回值是传值的函数,那么意味着getint会在函数返回出产生一个临时对象,用于存放局部变量i的值的一份拷贝(i的右值的拷贝),临时对象是没有名称的,其值(右值)会存储在调用者的“栈”中。即i虽然被销毁了,但它的拷贝仍然存在,并在函数返回时作为“右值”赋给”左值z“。
问题3:

char* c = getMemory();
printf("c = %s\n",c);   //hello world
char * getMemory()
{
    char *p = (char *)malloc(sizeof(char) * 20);
    strcpy(p,"hello world");
    return p;
}

变量包含两个值:左值和右值。左值是内存存储区的名字,右值是存放在存储区中的值。
首先解析下这个函数做了哪些事:
char p = (char )malloc(sizeof(char) * 20);表示分配一块“堆”内存并使得变量p指向这块“堆”内存;strcpy(p,”hello world”);表示往p所指向的“堆“内存中复制字符串。因此,return p;时,变量p的左值是一个局部指针,存储于函数栈上;右值是”堆“的地址(值时”hello world“)。
getMemory()属于返回值是传指针的函数,以为着会在函数返回处产生一个队返回变量p的”左值“的拷贝,而作为局部变量的p自然会在离开函数作用域时被销毁,但”左值“的拷贝是存在的,其存储了指向”堆
“的地址。
一般而言,malloc对应free来释放分配的堆内存,否则其内存将在程序运行期间一直存在,而此处的堆内存存放了hello world,显然我们想使用该字符串,因此并不需释放。

问题4:

const char* c1 = getMomery1();
printf("c1 = %s\n",c1); //hello world
char * getMomery1()
{
    char* p = "hello world";
    return p;
}

解析:
char* p = “hello world”;中,左值是局部指针变量p,存储于函数栈上,右值是字符串常量”hello world“,存储于常量存储区。
getMomery1()属于返回值是传指针的函数,意味着函数在返回处产生一个队返回对象的”左值“的拷贝,其值存储了指向字符串常量”hello world“的地址。该情况与问题3一致,不做过多解析。

问题5:

char* const c2 = getMemory2();
printf("c2 = %s\n",c2);

char* getMemory2()
{
    char p[] = "hello world";
    return p;
}

该函数返回值是传指针,会再函数返回时产生”左值“拷贝。
可以看到,p[]不是指针,是一个数组变量。编译器会根据右值”hello world“的长度在编译期间在函数的栈上为数组变量分配大小为12个字符的内存存储区,其值为”hello world“。
那么函数返回的”左值“拷贝指向的是局部变量数组p[12]的首地址。当局部数组p[12]离开作用域后会被自动销毁。这时,函数返回的”左值“拷贝指向的是一个被销毁的局部变量地址。
即结果为 c2 = 未知。

这里我们回顾下问题4跟问题5,可以看到指针c1 是const char* ,c2 是char * const。这里的区别引发出两个概念,常量指针、指针常量。
c1为常量指针,即指针指向的地址的内容是不可修改的。我的记法是,const在前, 表示指向的内容,那么在内容前加cosnt即表示了该内容为常量。
c2为指针常量,* 在 const前,const表示常量,而*表示这是指针类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值