了解内存布局
分区 | 存储数据 | 分配时机 | 释放时机 | 是否自动 |
---|---|---|---|---|
全局区 | 全局变量/常量 | 程序开始时 | 程序结束时 | 是 |
栈区 | 局部变量/常量 | 执行到定义位置 | 超出作用域范围 | 是 |
堆区 | 任意分配数据 | 手动分配 | 手动释放 | 否 |
代码区(文本区) | 代码实体(如:函数) | 程序开始时 | 程序结束时 | 是 |
堆与栈的区别
为什么要动态分配
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 就退出程序
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;
}
}