很多时候我们声明变量,系统就自动为我们处理好了内存。
这主要取决于内存分配发生的时间 和内存区域。C语言中主要有以下几种内存分配方式:
-
栈 (Stack) 内存分配 (自动内存):
-
何时发生: 在函数内部 声明的非static局部变量(包括基本类型如 int, float, char,以及固定大小的数组)。
-
特点:
-
内存分配和释放是自动的。当进入函数时,系统自动在栈上为这些变量分配空间;当函数返回时,这些空间自动被释放。
-
速度快。
-
空间有限。栈的大小通常是固定的(编译时或系统设定),如果分配过大的变量(比如一个非常大的数组),可能会导致栈溢出 (Stack Overflow)。
-
-
不需要 malloc 的情况:
-
声明一个简单的整数:int count = 0;
-
声明一个固定大小的字符数组:char buffer[1024];
-
声明一个固定大小的多维数组:short fixed_image[256][256];
-
变长数组 (VLA - C99及以后): int rows = 10; int cols = 5; int matrix[rows][cols]; 这种虽然大小在运行时确定,但内存通常还是在栈 上分配,同样有栈溢出的风险 ,且在C11后成为可选特性,可移植性降低。这是最接近你问题中场景但又不用 malloc 的情况,但有风险和限制。
-
-
-
堆 (Heap) 内存分配 (动态内存):
-
何时发生: 当你显式调用 malloc, calloc, realloc 等函数时。
-
特点:
-
内存分配和释放是手动 的。你需要调用 malloc 等来申请,并且必须 在不再需要时调用 free 来释放,否则会造成内存泄漏。
-
空间相对较大(受系统可用内存限制)。
-
分配/释放速度通常比栈慢。
-
可以灵活地在运行时决定分配多大的内存。
-
分配的内存生命周期可以跨越函数调用(只要不 free,它就一直有效)。
-
-
需要 malloc 的情况:
-
编译时不知道需要多大内存: 就像我们读取图像维度的例子,只有运行时读了文件才知道 width 和 height,才能计算出需要分配多大的空间来存像素。
-
需要分配非常大的内存: 即便编译时知道大小,如果数据量很大(比如几MB甚至GB的图像),放在栈上极有可能溢出,必须用 malloc 在堆上分配。
-
数据需要比创建它的函数活得更久: 如果一个函数需要创建一个数据结构(如图、链表节点)并将其返回给调用者使用,这个数据结构就必须在堆上分配,否则函数一返回,栈上的内存就被回收了。
-
-
-
静态/全局内存分配:
-
何时发生: 在函数外部 声明的变量(全局变量),或者在函数内部用 static 关键字声明的变量。
-
特点:
-
内存在程序加载时 (或编译时确定)分配,并在整个程序运行期间持续存在,直到程序结束才释放。
-
存储在程序的静态/全局数据区。
-
-
不需要 malloc 的情况:
-
全局变量:int global_counter = 0;
-
静态局部变量:void my_func() { static int call_count = 0; call_count++; }
-
-
总结:
-
当你在编译时就知道需要多少内存 ,并且这个量不大 时,通常直接声明变量(如 int x; 或 char buf[100];),系统会在栈或静态区自动处理,不需要 malloc。
-
当你只有在运行时才知道需要多少内存 ,或者需要的内存量非常大 ,或者希望内存的生命周期由你精确控制 (不受函数调用栈影响)时,就需要使用 malloc (以及配套的 free) 在堆上动态分配。
在我们读取遥感图像的例子中,因为图像尺寸 (width, height) 是从文件读取的(运行时才确定),而且图像数据可能很大,所以使用 malloc 在堆上分配内存是最合适、最安全、最灵活的方式。
因此介绍完上述理论之后,看待下面的示例问题就比较容易了:
C 语言(以及许多其他语言)中一个非常核心的概念:动态内存管理。
这句代码 pixels = (short *)malloc(num_pixels * sizeof(short)); 是进行动态内存分配的关键步骤。为什么需要它?主要原因如下:
-
程序运行时才能知道需要多大内存:
-
在编写代码并编译程序的时候,我们不知道 要处理的 thermal_image.bin 文件具体有多大。图像的宽度 (width) 和高度 (height) 是从文件开头读取 出来的,只有在程序运行时才能确定。
-
因此,我们无法在编译时就声明一个固定大小的数组来存储像素,比如 short pixels[10000]; (如果图像超过10000像素怎么办?) 或者 short pixels[height][width]; (在 C99 标准及以后,这种变长数组 (VLA) 理论上可行,但通常分配在栈 上,对于非常大的图像可能会导致栈溢出 (Stack Overflow),且 VLA 在 C11 后变为可选特性,可移植性降低)。
-
-
需要在"堆"(Heap)上分配内存:
-
程序运行时可以动态请求的内存来自于一个叫做堆(Heap)的大内存区域。与栈(Stack,通常用于函数调用、局部变量,空间有限且自动管理)不同,堆内存的分配和释放需要程序员手动管理。
-
malloc (Memory Allocation) 函数就是用来向操作系统请求一块指定大小的、连续的堆内存空间。
-
-
存储从文件读取的数据:
-
我们的目标是将二进制文件中的所有像素数据一次性读入内存进行处理(在这个例子里是计算相邻像素差)。
-
fread 函数需要一个已经存在的内存地址作为目标,用来存放它从文件中读取的数据。
-
因此,在调用 fread 读取像素数据之前,我们必须先通过 malloc 获得一块足够大的内存空间(num_pixels 个 short 类型数据所需的总字节数),并将这块内存的起始地址存储在指针 pixels 中。
-
总结来说,内存分配步骤 (malloc) 是必需的,因为它解决了以下问题:
-
大小不确定性: 在编译时无法预知需要存储多少像素数据。
-
存储空间需求: 需要一个足够大的地方来存放从文件读出的所有像素值,以便后续处理。
-
灵活性和健壮性: 使用堆内存分配使得程序能够处理不同尺寸的图像,避免了固定大小数组的限制和栈溢出的风险。
检查分配结果: if (!pixels) 检查 malloc 是否成功。如果系统内存不足,malloc 会返回 NULL,程序必须处理这种情况以避免崩溃。