内存管理

1.内存分布

img

img

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

关键点:

  1. 静态局部变量:static int a = 20; 表示 a 是一个静态局部变量,这意味着它会在函数的整个生命周期内保持其值,并且它的初始化只会在第一次调用时发生。不同于普通的局部变量,静态变量不会在每次函数调用结束时被销毁,而是会保留上一次的值。
  2. 变量的自增:a++; 这个操作会将 a 的值加 1。
  3. 打印值:printf("%d\n", a); 打印当前 a 的值。

调用过程:

  1. 第一次调用 fun():
    • 当 fun() 第一次被调用时,静态变量 a 会被初始化为 20(⭐只会初始化一次)。
    • 然后,a++ 将 a 的值加 1,a 变为 21。
    • 最后,printf 打印出 21。
  2. 第二次调用 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);

}

img

申请内存空间相关函数

#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);
}
posted @ 2025-05-08 14:34  cyt2730  阅读(38)  评论(0)    收藏  举报