# 7.1 存储管理函数

**Dynamic memory management**

**学习步骤：**

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700502074617-eb009560-9cef-44f8-ae04-ff886e88c6ff.png)

2023-11-23 周四

2024-03-24 Upload

## 7.1 malloc() / free() <a href="#r1hxn" id="r1hxn"></a>

`malloc()` 函数是 C 语言中用于动态分配内存的函数之一，它允许在程序运行时按需请求指定大小的内存空间。这个函数位于 `<stdlib.h>` 头文件中。

#### `malloc()` 函数的基本用法： <a href="#f92110d3" id="f92110d3"></a>

```
#include <stdlib.h>

void *malloc(size_t size);
```

* 参数： `size` 参数表示要分配的内存大小，以字节为单位。返回类型是 `void*`，即指向分配内存起始位置的指针。
* 返回值： 如果内存分配成功，返回指向分配内存的指针；如果分配失败，则返回 `NULL`。

#### 使用示例： <a href="#a0c1aa89" id="a0c1aa89"></a>

```c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;
    int n = 5;

    // 分配内存空间给指定大小的整型数组
    ptr = (int *)malloc(n * sizeof(int));

    if (ptr == NULL) {
        printf("内存分配失败！\n");
        return 1;
    }

    // 使用分配的内存空间
    for (int i = 0; i < n; ++i) {
        ptr[i] = i; // 对分配的内存进行写入
        printf("%d ", ptr[i]); // 读取并输出分配的内存内容
    }

    // 释放动态分配的内存
    free(ptr);
    ptr = NULL; // 建议将指针置为 NULL，以避免悬空指针

    return 0;
}
```

#### 注意事项： <a href="#id-1c7fa641" id="id-1c7fa641"></a>

1. 检查分配是否成功： 始终检查 `malloc()` 返回的指针是否为 `NULL`，以确保内存分配成功。
2. 释放动态分配的内存： 使用 `free()` 函数释放通过 `malloc()` 分配的内存，避免内存泄漏。
3. 类型转换： 在 C++ 中，不需要显式类型转换 `malloc()` 返回的指针，而在 C 中需要进行显式类型转换。

`malloc()` 是动态内存分配中最基本和常用的函数之一，但在使用时需要小心管理内存，避免内存泄漏和指针操作错误。随后的 C 标准引入了更多安全和方便的内存管理函数，如 `calloc()` 和 `realloc()`，它们可以更好地满足不同的需求。

当你使用 `malloc()` 分配了内存后，你可以将这块内存用于存储数据或进行其他操作。以下是一个示例，演示了如何正确分配内存并使用指针进行操作：

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    int *ptr = (int *)malloc(10 * sizeof(int)); // 分配足够存储 10 个整数的内存空间
    if (ptr == NULL) {
        printf("malloc failed: Unable to allocate memory\n");
        return 1;
    }

    // 将数组 arr 的内容复制到动态分配的内存中
    memcpy(ptr, arr, 10 * sizeof(int));

    // 打印动态分配内存中的内容
    for (int i = 0; i < 10; ++i) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // 释放动态分配的内存
    free(ptr);
    ptr = NULL; // 将指针置为 NULL，避免悬空指针

    return 0;
}
```

int \*ptr = (int \*)malloc(10 \* sizeof(int));: 使用 malloc() 函数分配了足够存储 10 个整数的内存空间，并将分配得到的地址赋值给了 ptr 指针。这行代码中的 (int \*) 是一种类型转换，将 malloc() 返回的 void\* 类型指针强制转换为 int\* 类型指针。

在这个示例中，我使用 `malloc()` 分配了足够存储 10 个整数的内存空间，并用 `memcpy()` 将数组 `arr` 的内容复制到了动态分配的内存中。然后通过循环打印了动态分配内存中的内容，最后使用 `free()` 释放了分配的内存。

这个例子展示了如何正确分配和使用动态内存。记得在不再需要动态分配的内存时使用 `free()` 函数来释放它，以免出现内存泄漏问题。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700780406882-35e498b0-a2ad-47f0-91f5-91fd5c414d86.png)

分配给一块空间，用，用完没有还，。 用去图书馆借书的例子理解， 借了，看完了，没还，别人也借不了。 然后我一直不还， 对图书馆来说，相当于书丢了，这就是“内存泄漏”。

开辟过程：

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700751553534-97d3af15-300d-47fa-aa7d-e83baeb81f9d.png)

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700779747695-e119a580-be53-4401-a615-fe0a6efcf414.png)

```c
// 将数组 arr 的内容复制到动态分配的内存中
    memcpy(ptr, arr, 10 * sizeof(int));

    // 打印动态分配内存中的内容
    for (int i = 0; i < 10; ++i) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // 释放动态分配的内存
    free(ptr);
    ptr = NULL; // 将指针置为 NULL，避免悬空指针
```

这段代码的作用是演示了动态内存的使用过程：

1. 内存复制： 使用 `memcpy()` 函数将数组 `arr` 中的内容复制到了动态分配的内存空间中，这样就把静态数组 `arr` 中的数据复制到了动态分配的内存中的位置 `ptr`。
2. 打印动态分配内存中的内容： 通过循环遍历动态分配内存中的内容，并使用 `printf()` 函数输出每个元素的值。
3. 释放动态分配的内存： 最后使用 `free()` 函数释放了动态分配的内存。这一步非常重要，因为在不再需要动态分配内存时，应该及时释放它，以免造成内存泄漏。
4. 将指针置为 NULL： 将指针 `ptr` 置为 `NULL` 是一种良好的习惯，可以避免出现悬空指针。悬空指针是指已经释放的内存却没有将指针设置为 `NULL`，这样的指针可能会导致程序错误的操作。

这段代码是一个简单的示例，展示了动态内存分配、使用和释放的基本流程。在实际编程中，使用动态内存时需要确保适当地分配和释放内存，避免内存泄漏和悬空指针的问题。

生成随机字符串： 这个例子对malloc的运用就很到位

<https://cplusplus.com/reference/cstdlib/malloc/>

```c
/* malloc example: random string generator*/
#include <stdio.h>      /* printf, scanf, NULL */
#include <stdlib.h>     /* malloc, free, rand */

int main ()
{
  int i,n;
  char * buffer;

  printf ("How long do you want the string? ");
  scanf ("%d", &i);

  buffer = (char*) malloc (i+1);
  if (buffer==NULL) exit (1);

  for (n=0; n<i; n++)
    buffer[n]=rand()%26+'a';
  buffer[i]='\0';

  printf ("Random string: %s\n",buffer);
  free (buffer);

  return 0;
}
```

`rand()%26+'a'` 是一个生成随机小写字母的表达式。让我解释一下：

* `rand()` 函数返回一个介于 0 和 `RAND_MAX` 之间的随机整数值。`RAND_MAX` 是 `<stdlib.h>` 头文件中定义的一个常量，表示 `rand()` 函数能够生成的最大随机数值。
* `rand()%26` 使用取余操作符 `%` 将 `rand()` 生成的随机整数限制在 0 到 25 之间。因为 `% 26` 可以得到 0 到 25 的余数。
* `'a'` 是字符 'a' 的 ASCII 值。在 ASCII 表中，小写字母 'a' 对应的 ASCII 值为 97。

所以，`rand()%26+'a'` 的含义是：生成一个 0 到 25 的随机整数，然后加上 ASCII 值 97（即小写字母 'a' 的 ASCII 值），得到的结果是一个随机小写字母的 ASCII 值。这个值会被存储在 `buffer[n]` 中，实现了生成随机小写字母的目的。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700780249757-c6e8648a-ed87-47a1-9727-f15d79c888cb.png)

这段代码是一个简单的示例，用于生成随机字符串。

1. `printf ("How long do you want the string? ");`: 打印提示消息，询问用户希望生成的随机字符串的长度。
2. `scanf ("%d", &i);`: 从用户输入中读取一个整数，存储到变量 `i` 中，表示用户期望生成的字符串长度。
3. `buffer = (char*) malloc (i+1);`: 使用 `malloc()` 函数分配了 `i+1` 大小的内存空间，存储字符的缓冲区。为了存储字符串结束符 `\0`，分配的大小比用户输入的长度多了一个字符位置。`buffer` 指针指向这块分配的内存空间。
4. `if (buffer==NULL) exit (1);`: 检查 `malloc()` 是否成功分配了内存。如果 `buffer` 是 `NULL`，意味着分配失败，程序将以错误码 1 退出。
5. `for (n=0; n<i; n++) buffer[n]=rand()%26+'a';`: 使用循环生成随机字符并存储在 `buffer` 指向的内存中。这里利用 `rand()` 函数生成一个 0 到 25 的随机数，加上字符 'a'，以便生成小写字母（ASCII 值从 97 到 122）。
6. `buffer[i]='\0';`: 在生成随机字符后，手动添加字符串结束符 `\0`，确保生成的内容构成一个以 `\0` 结尾的字符串。
7. `printf ("Random string: %s\n",buffer);`: 使用 `%s` 格式化符号将生成的随机字符串打印到控制台上。
8. `free (buffer);`: 使用 `free()` 函数释放了动态分配的内存，以免造成内存泄漏。

这个程序演示了如何动态分配内存，并在内存中生成随机字符串。在实际使用时，为了确保安全，应该考虑更多边界情况和错误处理，比如输入检查、内存分配失败时的应对等。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700803892730-155348c3-dfe2-4a30-b144-2a346666a839.png)

## 7.2 calloc <a href="#yevyd" id="yevyd"></a>

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700837496797-4f3d09dd-e7bf-44af-b30e-e9058077ca86.png)

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700837679983-e9724f57-d05a-46e7-9cac-1946c1d075bf.png)

`sizeof(int)` 就是表示整型 4

`p==NULL`表示 是否是悬空指针， 有没有申请成功，失败就结束 。`return 1` 。

```c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    int *ptr;

    printf("Enter the number of elements: ");
    scanf("%d", &n);

    // 使用 calloc 分配 n 个整数大小的内存空间
    ptr = (int *)calloc(n, sizeof(int));

    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    printf("Memory allocated successfully\n");

    // 打印分配的内存空间中的内容（此时应全为零）
    printf("Content of allocated memory:\n");
    for (int i = 0; i < n; ++i) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    // 释放动态分配的内存
    free(ptr);
    ptr = NULL;

    return 0;
}
```

这个示例演示了使用 calloc() 函数分配了 n 个整数大小的内存空间，然后打印了分配的内存中的内容。由于 calloc() 分配的内存会初始化为零，所以在打印内容之前，所有的元素都应该是零。

这段代码是一个基本的示例，展示了 calloc() 函数的用法。在实际使用时，可以根据需求修改分配的内存空间的类型和大小，并根据需要进行更复杂的操作。记得在不再需要动态分配的内存时，使用 free() 函数释放它，避免内存泄漏问题。

```
// 打印分配的内存空间中的内容（此时应全为零）
    printf("Content of allocated memory:\n");
    for (int i = 0; i < n; ++i) {
        printf("%d ", ptr[i]);
    }
    printf("\n");
```

让我解释下每行代码的作用：

1. printf("Content of allocated memory:\n");: 打印消息，说明即将输出分配的内存空间的内容。
2. for (int i = 0; i < n; ++i) { printf("%d ", ptr\[i]); }: 这个 for 循环遍历了动态分配的内存空间，依次输出每个元素的值。ptr\[i] 表示指针 ptr 指向的动态分配内存空间中第 i 个元素的值。
3. printf("\n");: 在 for 循环结束后，打印一个换行符，将输出的内容放在新的一行。

整个代码块的目的是，利用循环遍历打印了动态分配的内存空间中的内容，展示了 calloc() 函数初始化分配内存为零的特性。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700839889447-49555a33-c471-4ed3-9b8a-efb8b79c415d.png)

calloc 函数的典型用法， 就这样记住就行。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700839708804-003f0791-d358-4c0b-a209-47b49861205e.png)

```c
#include <stdio.h>
#include <string.h>

int main() {
    char str[20] = "Hello, World!";
    
    printf("Before memset: %s\n", str);

    // 将 str 的前 5 个字符（包括 '\0' 结尾符）设置为 '*'
    memset(str, '*', 5);

    printf("After memset: %s\n", str);

    return 0;
}
```

#### ![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700839914039-8a34d5ed-d4c3-4ddf-a4c4-b78983361ea5.png) <a href="#pefmg" id="pefmg"></a>

#### 注意事项： <a href="#nyscy" id="nyscy"></a>

* memset() 常用于将数组或其他内存块的一部分或全部内容设置为特定的值，比如清空数组、初始化某些数据结构等。
* 由于 value 参数是以整数形式给出的，因此将会被转换为 unsigned char 类型，所以可以用任何 unsigned char 类型表示的值来填充内存块，如 0、'A'、'\*' 等。
* 尽管 memset() 函数返回 void\* 类型的指针，但它实际上没有返回值。

## 7.3 realloc <a href="#gnonn" id="gnonn"></a>

#### realloc() 函数原型： <a href="#dvbif" id="dvbif"></a>

```
cCopy code
void *realloc(void *ptr, size_t size);
```

* ptr：指向之前由 malloc()、calloc() 或 realloc() 返回的内存块的指针。如果 ptr 是 NULL，则 realloc() 的行为类似于 malloc(size)。
* size：需要重新分配的内存块的新大小（以字节为单位）。

\ <br>

```c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    // 分配初始内存空间
    ptr = (int *)malloc(5 * sizeof(int));
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    printf("Memory allocated successfully\n");

    // 扩大内存空间
    ptr = (int *)realloc(ptr, 10 * sizeof(int)); // 10 * sizeof(int) >> 增加 50字节
    if (ptr == NULL) {
        printf("Memory reallocation failed\n");
        return 1;
    }

    printf("Memory reallocated successfully\n");

    free(ptr); // 释放内存

    return 0;
}
```

当在代码中加入数组时，我们可以利用 `realloc()` 函数动态调整数组的大小。以下是一个示例，展示了如何动态增加数组的长度：

```
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = NULL; // 初始化指针为 NULL，表示未分配内存

    int initialSize = 5;
    int newSize = 10;

    // 初始分配内存
    arr = (int *)malloc(initialSize * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    printf("Memory allocated successfully\n");

    // 假设在此之前已经对 arr 进行了赋值操作...

    // 重新分配更大的内存空间
    arr = (int *)realloc(arr, newSize * sizeof(int));
    if (arr == NULL) {
        printf("Memory reallocation failed\n");
        return 1;
    }

    printf("Memory reallocated successfully\n");

    // 使用新的内存空间...

    free(arr); // 释放内存

    return 0;
}
```

这个示例展示了如何在初始分配了一定大小的内存后，使用 `realloc()` 函数动态地调整数组的大小。在实际使用中，你可以根据需求动态改变数组的大小，使其适应不同的场景和需求。值得注意的是，`realloc()` 可能会涉及内存的重新分配和数据的拷贝，所以在使用时需要注意内存分配失败的情况，并确保操作前后正确管理内存。

`realloc` 在申请空间的时候，

情况一，覆盖到别人的空间： 情况一比较复杂

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700841317971-e1fb910d-82cf-451f-ad01-6281273ad70c.png)

怎么办？

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700841538080-3b30a1ee-ff8c-4207-864a-c8db75559efc.png)

找一块新的满足要求的空间， 第一步讲原来的值复制到新的空间去。 多的扩容的空间留着不用，返回的时候，直接返回新的地址。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700841840285-2a2154c4-0a2f-4e07-8f06-792d988827aa.png)

可以看到这里，realloc 没有使用 p来进行接收，这就是因为，如果内存申请失败，p指向申请的这块地址的时候，申请失败，指不过来， 那天之前的地址，可能会“失忆”，有风险。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700841995498-678a2e00-b31b-4107-829a-5941273a8f44.png)

这就ok了，做一个中间转换，

情况二，后面没人用，直接增加。情况二是简单的。

![](https://cdn.nlark.com/yuque/0/2023/png/1171985/1700841383640-db1d8eb5-ecf7-4439-bbbe-d727f3814c28.png)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://labspc.gitbook.io/cnippets/chap8.-wen-jian-du-xie-yu-nei-cun-guan-li/8.4-cun-chu-guan-li-han-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
