C 语言中的动态内存分配:malloc()、calloc()

⚡ 智能摘要

C 语言中的动态内存分配在运行时使用 stdlib.h 库中的 malloc()、calloc()、realloc() 和 free() 函数来保留和释放堆内存,从而允许程序在执行前需求未知的情况下灵活地调整数组和结构的大小。

  • 🧠 栈与堆: 基本变量使用自动栈,而动态内存位于堆上,并通过指针手动管理。
  • 📦 malloc()函数用来分配内存: 预留一个未初始化的、指定字节大小的块,并返回一个 void 指针,如果内存不足则返回 NULL。
  • 🔢 调用(): 为数组分配多个内存块,将每个字节设置为零,并返回指向第一个内存块的指针。
  • 🔄 realloc(): 将现有块放大或缩小,同时保留其原始内容,并将其放置在可能的新位置。
  • 🧹 自由的(): 将内存返回堆;每个 malloc() 或 calloc() 都需要一个匹配的 free() 来防止内存泄漏。
  • 🤖 人工智能帮助: AI 助手和 GitHub Copilot 会生成分配代码草稿,并标记缺失的 free() 调用和内存泄漏。

C 中的动态内存分配

C 语言中的内存管理如何工作?

当您使用基本数据类型声明变量时,C 编译器会自动在称为堆栈的内存池中为该变量分配内存空间。

例如,一个浮点变量在声明时通常占用 4 个字节(取决于平台)。我们可以使用 sizeof 运算符来验证此信息,如下例所示。

#include <stdio.h>
int main() { float x; printf("The size of float is %d bytes", sizeof(x)); return 0;}

输出将是:

 The size of float is 4 bytes

此外,具有指定大小的数组被分配在连续的内存块中,每个块都有一个元素的大小:

#include <stdio.h>
int main() { float arr[10];
printf("The size of the float array with 10 element is %d", sizeof(arr)); return 0;}

结果是:

 The size of the float array with 10 element is 40

如前所述,声明基本数据类型或数组时,内存会自动管理。然而,C 语言中有一种内存分配机制,允许你实现一个程序,其中数组的大小在程序运行时才确定。这种机制称为“动态内存分配”。

C 中的动态内存分配

动态内存分配是指根据编程需要手动分配和释放内存。动态内存通过指向堆(我们称之为堆)中新分配内存空间的指针进行管理和使用。

现在,您可以在运行时动态创建和销毁元素数组,而不会出现任何问题。总而言之,自动内存管理使用堆栈,而 C 动态内存分配使用堆。

这库具有负责动态内存管理的函数。

功能 目的
malloc() 分配请求大小的内存并返回指向第一个字节的指针
已分配空间。
调用() 为数组元素分配空间。将元素初始化为零并返回指向内存的指针。
重新分配() 用于修改先前分配的内存空间的大小。
自由的() 释放或清空先前分配的内存空间。

让我们讨论一下上述函数及其应用

C 语言中的 malloc() 函数

C malloc() 函数代表内存分配。它是一个用于动态分配内存块的函数。它保留指定大小的内存空间并返回指向内存位置的空指针。返回的指针通常为 void 类型。这意味着我们可以将 C malloc() 函数分配给任何指针。

malloc() 函数的语法:

ptr = (cast_type *) malloc (byte_size);

在这里,

  • ptr 是 cast_type 的指针。
  • C malloc() 函数返回指向分配的 byte_size 内存的指针。

malloc()的示例:

Example: ptr = (int *) malloc (50)

当该语句执行成功后,会保留50字节的内存空间,保留空间第一个字节的地址赋给int类型的指针ptr。

考虑另一个例子:

#include <stdlib.h>
int main(){
int *ptr;
ptr = malloc(15 * sizeof(*ptr)); /* a block of 15 integers */
    if (ptr != NULL) {
      *(ptr + 5) = 480; /* assign 480 to sixth integer */
      printf("Value of the 6th integer is %d",*(ptr + 5));
    }
}

输出:

Value of the 6th integer is 480

C malloc() 函数

  1. 请注意,这里使用了 sizeof(*ptr) 而不是 sizeof(int),目的是为了在稍后将 *ptr 声明强制转换为不同的数据类型时,使代码更加健壮。
  2. 如果内存不足,分配可能会失败。在这种情况下,它会返回一个 NULL 指针。因此,您应该包含代码来检查 NULL 指针。
  3. 请记住,分配的内存是连续的,可以将其视为数组。我们可以使用指针算法来访问数组元素,而不是使用括号 [ ]。我们建议使用 + 来引用数组元素,因为使用增量 ++ 或 += 会更改数组存储的地址 指针.

Malloc() 函数还可以与字符数据类型以及结构等复杂数据类型一起使用。

C 语言中的 free() 函数

记忆 变量 在编译时自动释放。在动态内存分配中,您必须明确释放内存。如果不这样做,您可能会遇到内存不足错误。

在 C 语言中,调用 free() 函数来释放/释放内存。通过释放程序中的内存,可以为以后使用更多内存腾出空间。

例如:

#include <stdio.h>
int main() {
int* ptr = malloc(10 * sizeof(*ptr));
if (ptr != NULL){
  *(ptr + 2) = 50;
  printf("Value of the 2nd integer is %d",*(ptr + 2));
}
free(ptr);
}

输出:

 Value of the 2nd integer is 50

C 语言中的 calloc() 函数

C calloc() 函数代表连续分配。此函数用于分配多个内存块。它是一个动态内存分配函数,用于将内存分配给复杂的数据结构,例如数组和结构。

Malloc() 函数用于分配单块内存空间,而 C 中的 calloc() 用于分配多块内存空间。calloc() 函数分配的每个块大小相同。

calloc() 函数的语法:

ptr = (cast_type *) calloc (n, size);

上述语句用于分配n个大小相同的内存块。

分配内存空间后,所有字节都被初始化为零。

返回当前位于分配的内存空间第一个字节的指针。

每当分配内存空间出现错误(例如内存不足)时,就会返回空指针。

calloc()的示例:

下面的程序计算算术序列的总和。

#include <stdio.h>
    int main() {
        int i, * ptr, sum = 0;
        ptr = calloc(10, sizeof(int));
        if (ptr == NULL) {
            printf("Error! memory not allocated.");
            exit(0);
        }
        printf("Building and calculating the sequence sum of the first 10 terms \ n ");
        for (i = 0; i < 10; ++i) { * (ptr + i) = i;
            sum += * (ptr + i);
        }
        printf("Sum = %d", sum);
        free(ptr);
        return 0;
    }

结果:

Building and calculating the sequence sum of the first 10 terms
Sum = 45

calloc() 与 malloc():主要区别

以下是 malloc() 与 calloc() 在C中:

calloc() 函数通常比 malloc() 函数更合适、更高效。虽然这两个函数都用于分配内存空间,但 calloc() 可以一次分配多个块。您不必每次都请求一个内存块。calloc() 函数用于需要更大内存空间的复杂数据结构。

C 语言中 calloc() 分配的内存块始终初始化为零,而在 C 语言中的 malloc() 函数中,它始终包含垃圾值。

C 语言中的 realloc() 函数

使用 C 语言的 realloc() 函数,您可以向已分配的内存块添加更多内存。它会扩展当前内存块,同时保持原有内容不变。realloc() 在 C 语言中代表内存重新分配 (relocation of memory)。

realloc() 还可用于减少先前分配的内存的大小。

realloc() 函数的语法:

ptr = realloc (ptr,newsize);

上述语句在变量 newsize 中分配了一个新的具有指定大小的内存空间。执行该函数后,指针将返回到内存块的第一个字节。新大小可以大于或小于以前的内存。我们无法确定新分配的块是否指向与前一个内存块相同的位置。此函数将复制新区域中所有以前的数据。它确保数据保持安全。

realloc()的示例:

#include <stdio.h>
int main () {
   char *ptr;
   ptr = (char *) malloc(10);
   strcpy(ptr, "Programming");
   printf(" %s,  Address = %u\n", ptr, ptr);

   ptr = (char *) realloc(ptr, 20); //ptr is reallocated with new size
   strcat(ptr, " In 'C'");
   printf(" %s,  Address = %u\n", ptr, ptr);
   free(ptr);
   return 0;
}

每当C语言中的realloc()操作不成功时,它都会返回一个空指针,并且先前的数据也会被释放。

C 中的动态数组

C 语言中的动态数组允许元素数量根据需要增加。C 动态数组广泛用于计算机科学算法。

在以下程序中,我们创建并调整了一个动态数组 C

#include <stdio.h>
    int main() {
        int * arr_dynamic = NULL;
        int elements = 2, i;
        arr_dynamic = calloc(elements, sizeof(int)); //Array with 2 integer blocks
        for (i = 0; i < elements; i++) arr_dynamic[i] = i;
        for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
        elements = 4;
        arr_dynamic = realloc(arr_dynamic, elements * sizeof(int)); //reallocate 4 elements
        printf("After realloc\n");
        for (i = 2; i < elements; i++) arr_dynamic[i] = i;
        for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
        free(arr_dynamic);
    }

屏幕上的 C 动态数组程序结果:

 
arr_dynamic[0]=0
arr_dynamic[1]=1
After realloc
arr_dynamic[0]=0
arr_dynamic[1]=1
arr_dynamic[2]=2
arr_dynamic[3]=3

常见问题

当 malloc() 或 calloc() 分配的堆内存从未被释放时,就会发生内存泄漏。为了防止内存泄漏,每次分配内存后都必须调用 free() 函数,一旦该内存块不再需要,就立即释放它。

悬空指针仍然持有已被 free() 函数释放的内存地址。解引用它会导致未定义行为,因此为了安全起见,在释放内存后应立即将指针赋值为 NULL。

不,C 语言没有垃圾回收机制,所以堆内存不会被自动回收。程序员必须使用 `free()` 函数显式释放每个内存块;否则,分配的内存会一直保留到程序终止。

不。对同一个地址调用两次 free() 函数是双重释放,会导致未定义行为或程序崩溃。在第一次调用 free() 后将指针设置为 NULL 可以避免后续的 free() 调用造成危害。

`realloc(NULL, size)` 的作用与 `malloc(size)` 完全相同,都会分配一个全新的内存块。`realloc(ptr, 0)` 会释放该内存块,因此务必捕获返回的指针,而不是覆盖原始指针。

Valgrind、AddressSanitizer 和 Dr. Memory 等工具 trac记录所有未释放的分配和报告块,包括确切的分配行,并进行相应的检查。ping 在发布前找到泄漏点。

是的。AI 代码助手可以根据提示自动生成 malloc()、calloc() 和 free() 函数序列,解释指针错误,并标记缺失的 free() 调用。在信任生成的代码之前,务必先编译并测试。

GitHub 副驾驶 根据注释建议分配块、NULL 检查和匹配的 free() 调用;在发布之前检查每个完成项是否存在泄漏和大小是否正确。

总结一下这篇文章: