内存管理
1.内存分布
void func(int a, int *p) // 在函数 func 的栈内存中分配
{
double f1, f2; // 在函数 func 的栈内存中分配
... // 退出函数 func 时,系统的栈向上缩减,释放内存
}
int main(void)
{
int m = 100; // 在函数 main 的栈内存中分配
func(m, &m); // 调用func时,系统的栈内存向下增长
}
2.静态数据
1.静态全局变量(与全局变量区别只能在本文中使用)
2.静态局部变量(存储在数据段)
3.静态数据只初始化一次(只申请一次内存空间)
练习:
#include <stdio.h>
static int a=10;
void fun()
{
static int a=20;
a++;
printf("%d\n",a);
}
int main(void)
{
fun();//输出:21
fun();//输出:22
return 0;
}
在这段代码中,fun() 函数定义了一个 静态局部变量 a,并且在每次调用时都会对其进行自增操作。让我们逐步分析这个函数的行为:
void fun()
{
static int a=20;
a++;
printf("%d\n",a);
}
关键点:
- 静态局部变量:
static int a = 20;表示a是一个静态局部变量,这意味着它会在函数的整个生命周期内保持其值,并且它的初始化只会在第一次调用时发生。不同于普通的局部变量,静态变量不会在每次函数调用结束时被销毁,而是会保留上一次的值。 - 变量的自增:
a++;这个操作会将a的值加 1。 - 打印值:
printf("%d\n", a);打印当前a的值。
调用过程:
- 第一次调用 fun():
- 当
fun()第一次被调用时,静态变量a会被初始化为 20(⭐只会初始化一次)。 - 然后,
a++将a的值加 1,a变为 21。 - 最后,
printf打印出 21。
- 当
- 第二次调用 fun():
- 由于
a是静态变量,它的值在第一次调用后保留。因此,第二次调用时,a的值仍然是 21(⭐而不是重新初始化为 20)。 a++会将a增加 1,变成 22。- 然后,
printf打印出 22。
- 由于
3.堆内存
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int a=123; //栈
int b=456; //栈
printf("%p, %p\n", &a, &b);
//堆空间
char *aptr = malloc(16);
char *bptr = malloc(4);
printf("%p, %p\n", aptr, bptr);
}
申请内存空间相关函数
#include <stdlib.h>
void *malloc(size_t size);
//申请一段连续内存空间(指定字节数)
//返回值:成功-指向堆空间的首地址,失败-NULL
//参数:申请堆空间大小字节为单位
void free(void *ptr);
//参数:要释放的堆空间首地址
void *calloc(size_t nmemb, size_t size);
//申请数组空间(指定数组元素个数,每一个元素大小), 并且会清空数据
//返回值:成功-指向堆空间的首地址,失败-NULL, 自动清空申请的内存
//参数:nmemb:数组元素个数, size:数组每一个元素大小
void *realloc(void *ptr, size_t size);
//在原有内存基础上申请(可以把原来的内存变大,或者缩小)
//返回值:成功-返回新的堆空间首地址,失败-NULL
//参数: ptr原来堆空间首地址, size需要更改为的目标大小
free后原来的指针还能访问,如果内存没变,有可能还是之前的数据。
练习
阅读下面代码, 说明运行结果和原因
int *getmem1(int size)
{
int data[size];
return data;
}
void getmem2(int *p, int size){
p = malloc(size*4);
}
int *getmem3(int size){
int *p = malloc(4*size);
return p;
}
int main(void){
//第一问
int *p1 = getmem1(10);
p1[0] = 1; //运行时报错为段错误
printf("%d\n", p1[0]);
//第二问
int* p2 = NULL;
getmem2(p2, 10);
p2[0] = 2; //运行时报错为段错误
printf("%d\n", p2[0]);
//第三问
int *p3 = getmem3(10);
p3[0] = 3;
printf("%d\n",p3[0]);
}
1. getmem1(int size) 函数
int *getmem1(int size)
{
int data[size];
return data; // 该函数结束后,data会被销毁,因为它是栈上分配的内存
}
- 问题:
data是一个栈上分配的数组。栈上的内存是临时的,当函数结束时,栈上的局部变量会被销毁。因此,在getmem1函数返回时,data数组的内存会被释放,p1实际上指向了已经被销毁的内存区域。- 在
main函数中访问p1[0]会导致 段错误 (Segmentation Fault),因为p1指向一个已释放的内存区域。
- 总结: 不要返回局部数组或栈上的数据,因为它们在函数返回时会失效。
2. getmem2(int *p, int size) 函数
void getmem2(int *p, int size){
p = malloc(size * 4); // malloc 会给p分配内存
}
- 问题:
- 在
getmem2函数中,传入的p是一个指针参数。malloc为p分配了内存,但这只改变了p的局部值。实际上,p只是getmem2函数的局部副本,而不是main函数中的p2。 - 这意味着
main中的p2依然没有指向正确的内存位置,p在getmem2函数返回后也会被销毁。因此,p2在main中没有被成功赋值,仍然是NULL。 - 然后在
main函数中,p2[0] = 2;会触发段错误,因为p2没有指向有效的内存。
- 在
- 总结: 当传递指针给函数时,如果你想修改传入指针的值(例如,让它指向
malloc分配的内存),应该传递指针的指针(即int**类型),而不是直接传递一个指针。
3. getmem3(int size) 函数
int *getmem3(int size){
int *p = malloc(4 * size);
return p;
}
- 问题:
- 在
getmem3中,使用malloc为p分配了堆上的内存。堆上的内存直到显式释放之前是有效的。因此,p被正确地分配了内存并返回。 - 在
main函数中,p3被赋值为getmem3(10)的返回值,p3成功指向分配的内存。之后,p3[0] = 3;就不会引发段错误,而是成功地在这块内存中存储值 3。
- 在
- 总结: 堆上分配的内存(通过
malloc)在整个程序中有效,直到你显式释放它。如果需要返回数据或者在多个地方使用数据,堆上分配的内存是更合适的选择。
总结:
- getmem1:返回栈上的数组会导致访问无效内存,触发段错误。
- getmem2:传递指针时修改指针的值失败,
p2没有被正确赋值。 - getmem3:返回堆上的内存是正确的,并且内存不会在函数结束时被销毁。
正确的做法是使用堆内存(如 malloc)来管理动态内存分配,避免使用栈内存返回指针。
作业
编写一个 C 程序,实现一个简单的动态数组。要求使用堆内存来存储数组元素,并提供函数来动态增加和减少数组的大小
int * array_create(int size){
int *p = calloc(size , sizeof(int));
return p;
}
void array_init(int *array, int size)
{
for(int i=0; i<size; i++){
array[i] = random()%100;
}
}
void array_show(int *array, int size)
{
for(int i=0; i<size; i++){
printf("%d ",array[i] );
}printf("\n");
}
int *array_change(int *array , int destsize){
array = realloc(array, destsize);
return array;
}
int main(void){
int *array = array_create(10); //创建一个数组
array_init(array, 10); //初始化
array_show(array, 10);
array = array_change(array, 20);
array_init(array+10, 10); //初始化
array_show(array, 20);
}
本文来自博客园,作者:cyt2730,转载请注明原文链接:https://www.cnblogs.com/cytwjyy/p/18866246

浙公网安备 33010602011771号