Skip to content

了解内存布局

分区存储数据分配时机释放时机是否自动
全局区全局变量/常量程序开始时程序结束时
栈区局部变量/常量执行到定义位置超出作用域范围
堆区任意分配数据手动分配手动释放
代码区(文本区)代码实体(如:函数)程序开始时程序结束时

堆与栈的区别

推荐阅读

为什么要动态分配

c
int main() {
  int val = 10;       // 申请 4  个字节的空间
  int nums[10] = {1}; // 申请 40 个字节的空间
  // 这样的内存申请方式的弊端是 10 是固定的
  // 如果后续我需要更多的空间, 或者我只使用4个
  // 字节空间, 那么其他的空间就是多余的


  // 比如: 请完成这样的程序
  // 1. 保存用户输入数字(并输出), 直到用户输出 -1 结束
  // 用固定的数组就无法做了, 你如何知道用户会输入多少个数字呢?
  // 太大了浪费内存, 太小了又不够, 所以就需要动态的分配内存
}

sizeof 函数

  • 可以获取数据类型的字节大小
  • 可以获取变量占用的内存空间字节大小
c
#include <stdio.h>

int main() {
  printf("sizeof char: %lu\n", sizeof(char));   // 1
  printf("sizeof int: %lu\n", sizeof(int));     // 4
  printf("sizeof float: %lu\n", sizeof(float)); // 4

  char str1[] = "hello";
  char str2[] = {'w', 'o', 'r', 'l', 'd', '\0'};
  printf("sizeof str1 = %lu \n", sizeof(str1)); // 6, 因为最后有一个 '\0'
  printf("sizeof str2 = %lu \n", sizeof(str2)); // 6
}

malloc 和 free

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

int main() {
  /***** 1. (int *) 什么意思?
   * malloc 返回的是 void *: 表示任意数据类型的指针
   * (int *) 表示将 void * 强制类型转换为 int 类型的指针
   */

  /**** 2. 为什么这样写 sizeof(int) 而不是直接给个 4?
   * 4 只是表示有4个字节的内存空间, 没有没有明确指出存储的
   * 是什么数据, float 也是占4个字节, 如何知道存储的是 int 还是 float?
   * sizeof(int) 明确指出是存储 int 类型数据大小的内存空间
   */
  int *p = (int *)malloc(sizeof(int));

  /**** 3. 为什么需要做这个判断
   * 因为申请内存不一定就能够成功: 比如我申请一个超过物理内存
   * 大小的内存空间, 如:malloc(INT_MAX), 那么此时就会申请失败
   * 如果申请失败了,就无法用这个指针来操作内存空间,导致代码报错
   */
  if (p == NULL) {
    printf("memory assign fail");
    return 1;
  }

  /***** 4. 这样分配内存然后赋值和直接 int p = 123 有什么区别?
   * 直接声明变量然后赋值, 数据是存放在栈区的(超出作用域会自动释放掉)
   * 而手动分配内存是存放在堆区的,需要手动管理
   */
  *p = 123;
  printf("p saved value = %d \n", *p);

  /**** 5. 为什么需要释放内存
   * 内存空间是有限制的, 如果申请了内存空间但是不释放就会导致
   * 内存泄漏, 最后导致内存溢出
   *
   * 内存泄漏: 内存已经不再使用了,但是却没有释放
   * 内出溢出: 需要的内存超出物理内存设备,内存被挤爆了
   */
  free(p);

  /**** 6.为什么要执行 p = NULL 这个操作?
   * 为了防止重复释放(多次执行 free(p)), 重复释放可能会导致代码报错
   * 当将指针设置为空指针后, 就能避免重复释放指针导致的报错
   */
  p = NULL;

  // 如果没有重置指针为空指针的操作, 这些重复释放就会导致报错
  // malloc: *** error for object 0x6000009bc040: pointer being freed was not
  // allocated
  free(p);
  free(p);
  free(p);
  free(p);
  free(p);
  return 0;
}

动态分配

  1. 记录用户输入数字, 输入一个就放到内存中, 并且输出
  2. 直到输出 -1 就退出程序
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void print_nums(int *nums, unsigned int count) {
  printf("你输入的数字为: ");
  for (unsigned int i = 0; i < count; i++) {
    printf(" %d ", nums[i]);
  }
  printf("\n");
}

int main() {
  int *nums = NULL;
  unsigned int index = 0; // 当前是第n次输入

  // 默认分配一个空间
  nums = (int *)malloc(sizeof(int));
  if (nums == NULL) {
    printf("内存分配失败\n");
    return 1;
  }

  printf("请输入需要输出的数字, 输出-1退出: \n");
  while (1) {
    int input;
    scanf("%d", &input);
    if (input == -1) {
      break;
    }

    // 第二次输入需要2个空间 ... 第n次输入需要n个
    if (index > 0) {
      // 动态的增加内存: 根据需要的内存大小, 再次分配内存
      int *new_nums = malloc(sizeof(int) * (index + 1));
      if (new_nums == NULL) {
        printf("内存分配失败\n");
        return 1;
      }

      // 从 nums 内存空间中复制 sizeof(int) * index
      // 个字节的内容到 new_nums 内存空间
      memcpy(new_nums, nums, sizeof(int) * index);

      // 释放旧内存, 使用新内存
      free(nums);
      nums = new_nums;
    }

    // 设置此次输入的内容
    nums[index] = input;

    // 改变索引, 让下次分配正确的空间个数
    index++;
    print_nums(nums, index);
  }

  // 最后释放所有动态分配的内存
  free(nums);
  nums = NULL;

  return 0;
}

malloc 与 realloc

在之前的代码中发现这个重新分配内存的操作, 需要我们手动将原来的内存内容复制到新的内存内容中, 释放原来的内存空间使用新内存空间, 这个操作是比较繁琐的, 那么 realloc 就是简化这个操作的, 虽然操作是简化了, 但是原理是不变的 还是重新 malloc 然后将原来的数据复制过来

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

void print_nums(int *nums, unsigned int count) {
  printf("你输入的数字为: ");
  for (unsigned int i = 0; i < count; i++) {
    printf(" %d ", nums[i]);
  }
  printf("\n");
}

int main() {
  int *nums = NULL;
  unsigned int index = 0; // 当前是第n次输入

  // 默认分配一个空间
  nums = (int *)malloc(sizeof(int));
  if (nums == NULL) {
    printf("内存分配失败\n");
    return 1;
  }

  printf("请输入需要输出的数字, 输出-1退出: \n");
  while (1) {
    int input;
    scanf("%d", &input);
    if (input == -1) {
      break;
    }

    // 第二次输入需要2个空间 ... 第n次输入需要n个
    if (index > 0) {
      // 动态的增加内存
      int *new_nums = realloc(nums, sizeof(int) * (index + 1));
      if (new_nums == NULL) {
        printf("内存分配失败\n");
        return 1;
      }

      /** 使用 realloc 函数后, 就不需要手动复制数据
      * 这个操作了, realloc 会自动将原来的数据全部复制过来
      * memcpy(new_nums, nums, sizeof(int) * index);
      * free(nums);
      */

      nums = new_nums;
    }

    // 设置此次输入的内容
    nums[index] = input;

    // 改变索引, 让下次分配正确的空间个数
    index++;
    print_nums(nums, index);
  }

  // 最后释放所有内存
  free(nums);
  nums = NULL;

  return 0;
}

分配和释放内存封装

利用函数配合宏,就可以大大简化分配内存和释放内存的操作 可以将这个文件单独放到一个头文件中, 由于我这个是笔记 为了直观的看到效果,方便记录,我会将所有文件放到一起

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

// 封装malloc
void *safe_malloc(size_t num, size_t size) {
  void *ptr = malloc(num * size);
  if (ptr == NULL) {
    fprintf(stderr, "Memory allocation failed! %s:%d\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  return ptr;
}

#define MALLOC(type, num)                                                      \
  ({                                                                           \
    type *result = (type *)safe_malloc((num), sizeof(type));                   \
    result;                                                                    \
  })

// 封装realloc
void *safe_realloc(void **old_ptr, size_t num, size_t size) {
  void *new_ptr = realloc(*old_ptr, num * size);
  if (new_ptr == NULL) {
    fprintf(stderr, "Memory reallocation failed! %s:%d\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  *old_ptr = new_ptr; // 更新原始指针
  return new_ptr;
}

#define REALLOC(old_ptr, type, num)                                            \
  ({                                                                           \
    type *result =                                                             \
        (type *)safe_realloc((void **)(old_ptr), (num), sizeof(type));         \
    result;                                                                    \
  })

// 封装free
void safe_free(void **ptr) {
  if (*ptr != NULL) {
    free(*ptr);
    *ptr = NULL;
  }
}

#define FREE(ptr) safe_free((void **)&(ptr))

// 输出内存中的内容
void print_nums(int *ptr, int count) {
  for (int i = 0; i < count; i++) {
    printf("%d ", ptr[i]);
  }
  printf("\n");
}

int main() {
  // 分配10个int类型的内存空间大小并填充数据
  int count = 10;
  int *ptr = MALLOC(int, count);
  for (int i = 0; i < count; i++) {
    ptr[i] = i;
  }

  // 输出内存中的内容
  print_nums(ptr, count);

  // 重新分配空间, 并继续填充数据
  count = 20;
  REALLOC(&ptr, int, count);

  // 从10开始填充即可, 前面的数据会自动复制过来
  for (int i = 10; i < 20; ++i) {
    ptr[i] = i;
  }

  // 输出内存中的内容
  print_nums(ptr, count);

  // 释放内存并将指针设为 NULL
  FREE(ptr);
  return 0;
}
c
#include "mem.h"
#include <stdio.h>

// 输出内存中的内容
void print_nums(int *ptr, int count) {
  for (int i = 0; i < count; i++) {
    printf("%d ", ptr[i]);
  }
  printf("\n");
}

int main() {
  // 分配10个int类型的内存空间大小并填充数据
  int count = 10;
  int *ptr = MALLOC(int, count);
  for (int i = 0; i < count; i++) {
    ptr[i] = i;
  }

  // 输出内存中的内容
  print_nums(ptr, count);

  // 重新分配空间, 并继续填充数据
  count = 20;
  REALLOC(&ptr, int, count);

  // 从10开始填充即可, 前面的数据会自动复制过来
  for (int i = 10; i < 20; ++i) {
    ptr[i] = i;
  }

  // 输出内存中的内容
  print_nums(ptr, count);

  // 释放内存并将指针设为 NULL
  FREE(ptr);
  return 0;
}
c
#include <stdio.h>

// 定义封装 malloc 函数
void *safe_malloc(size_t num, size_t size);

// 定义封装 realloc 函数
void *safe_realloc(void **old_ptr, size_t num, size_t size);

// 定义封装 realloc 函数
void safe_free(void **ptr);

// 分配内存宏, ifndef 防止重复定义
#ifndef MALLOC
#define MALLOC(type, num)                                                      \
  ({                                                                           \
    type *result = (type *)safe_malloc((num), sizeof(type));                   \
    result;                                                                    \
  })
#endif // MALLOC

// 重新分配内存宏
#ifndef REALLOC
#define REALLOC(old_ptr, type, num)                                            \
  ({                                                                           \
    type *result =                                                             \
        (type *)safe_realloc((void **)(old_ptr), (num), sizeof(type));         \
    result;                                                                    \
  })
#endif // REALLOC

// 释放内存宏
#ifndef FREE
#define FREE(ptr) safe_free((void **)&(ptr))
#endif // FREE
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mem.h"

// 封装malloc
void *safe_malloc(size_t num, size_t size) {
  void *ptr = malloc(num * size);
  if (ptr == NULL) {
    fprintf(stderr, "Memory allocation failed! %s:%d\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  return ptr;
}

// 封装realloc
void *safe_realloc(void **old_ptr, size_t num, size_t size) {
  void *new_ptr = realloc(*old_ptr, num * size);
  if (new_ptr == NULL) {
    fprintf(stderr, "Memory reallocation failed! %s:%d\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  *old_ptr = new_ptr; // 更新原始指针
  return new_ptr;
}

// 封装free
void safe_free(void **ptr) {
  if (*ptr != NULL) {
    free(*ptr);
    *ptr = NULL;
  }
}

Released under the MIT License.