7.1 存储管理函数

Dynamic memory management

学习步骤:

2023-11-23 周四

2024-03-24 Upload

7.1 malloc() / free()

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

malloc() 函数的基本用法:

#include <stdlib.h>

void *malloc(size_t size);
  • 参数: size 参数表示要分配的内存大小,以字节为单位。返回类型是 void*,即指向分配内存起始位置的指针。

  • 返回值: 如果内存分配成功,返回指向分配内存的指针;如果分配失败,则返回 NULL

使用示例:

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

注意事项:

  1. 检查分配是否成功: 始终检查 malloc() 返回的指针是否为 NULL,以确保内存分配成功。

  2. 释放动态分配的内存: 使用 free() 函数释放通过 malloc() 分配的内存,避免内存泄漏。

  3. 类型转换: 在 C++ 中,不需要显式类型转换 malloc() 返回的指针,而在 C 中需要进行显式类型转换。

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

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

#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() 函数来释放它,以免出现内存泄漏问题。

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

开辟过程:

// 将数组 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/

/* 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] 中,实现了生成随机小写字母的目的。

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

  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() 是否成功分配了内存。如果 bufferNULL,意味着分配失败,程序将以错误码 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() 函数释放了动态分配的内存,以免造成内存泄漏。

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

7.2 calloc

sizeof(int) 就是表示整型 4

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

#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() 函数初始化分配内存为零的特性。

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

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

注意事项:

  • memset() 常用于将数组或其他内存块的一部分或全部内容设置为特定的值,比如清空数组、初始化某些数据结构等。

  • 由于 value 参数是以整数形式给出的,因此将会被转换为 unsigned char 类型,所以可以用任何 unsigned char 类型表示的值来填充内存块,如 0、'A'、'*' 等。

  • 尽管 memset() 函数返回 void* 类型的指针,但它实际上没有返回值。

7.3 realloc

realloc() 函数原型:

cCopy code
void *realloc(void *ptr, size_t size);
  • ptr:指向之前由 malloc()、calloc() 或 realloc() 返回的内存块的指针。如果 ptr 是 NULL,则 realloc() 的行为类似于 malloc(size)。

  • size:需要重新分配的内存块的新大小(以字节为单位)。

#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 在申请空间的时候,

情况一,覆盖到别人的空间: 情况一比较复杂

怎么办?

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

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

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

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

Last updated