当前位置: 首页 > news >正文

《深度讲解 C 语言动态内存:函数用法、错误规避与经典笔试题》

目录

一. 动态内存分配的意义

二. malloc和free

2.1 malloc 函数:动态内存申请

2.2 free 函数:动态内存释放

2.3 malloc和free的使用

三. calloc 与 realloc:C 语言动态内存管理的扩展函数

3.1 calloc 函数:初始化的内存分配

3.2 realloc 函数:动态调整内存大小

四. 常见的动态内存错误

4.1 对 NULL 指针的解引用操作

4.2 动态内存的越界访问

4.3  对非动态内存使用 free 释放

4.4  动态内存的重复释放

4.5  动态内存泄漏

4.6  动态内存释放后继续使用(野指针操作)

总结:动态内存错误的核心规避原则

五. 动态内存经典笔试题分析

5.1  题目1

5.2  题目2

5.3  题目3

5.4  题目4

六.  C/C++中程序内存区域划分


一.动态内存分配的意义

动态内存分配是程序在运行过程中根据实际需求灵活申请、使用和释放内存的机制,其核心意义在于解决静态内存分配的局限性,让内存管理更贴合程序动态变化的需求

二.malloc和free

在 C 语言中,malloc 和 free 是标准库 <stdlib.h> 提供的一对核心函数,专门用于动态内存的 “申请” 与 “释放”,是实现动态数据结构(如动态顺序表、链表)、适配数据量动态变化的关键工具。二者需配合使用,以确保内存合理分配且避免内存泄漏。

2.1 malloc 函数:动态内存申请

malloc(memory allocate,内存分配)的核心作用是在程序运行时,从 “堆内存” 中申请一块指定大小的连续内存空间,并返回指向该空间起始地址的指针。

函数原型

void* malloc(size_t size);
  • 参数 size_t size:需申请的内存字节数(size_t 是无符号整数类型,通常与 unsigned int 等价)。例如申请 10 个 int 类型的空间,需传入 10 * sizeof(int)sizeof(int) 确保适配不同平台的 int 字节数,如 32 位平台为 4 字节,64 位平台仍为 4 字节)。
  • 返回值 void*
    • 成功:返回指向申请到的内存空间的起始地址(void* 是通用指针类型,需强制转换为具体数据类型的指针才能使用,如 int*struct SeqList*);
    • 失败:返回 NULL(通常因堆内存不足导致,必须检查返回值以避免空指针访问)。

 核心特性

  • 申请的是 “连续内存”malloc 分配的内存是连续的,可满足数组、结构体等需连续存储的数据结构需求(如动态顺序表的数组空间)。
  • 不初始化内存:申请到的内存中存储的是随机的 “垃圾值”(原内存区域的残留数据),若需初始化,需手动赋值(如 memset 函数)或直接覆盖写入数据。
  • 堆内存分配:内存来自 “堆区”(而非栈区或全局区),生命周期由程序员控制(不随函数作用域结束而自动释放,需用 free 手动释放)。

2.2 free 函数:动态内存释放

free 的核心作用是将 malloc(或 callocrealloc)申请的堆内存 “归还” 给系统,避免内存长期占用导致 “内存泄漏”(内存泄漏:申请的内存未释放,程序运行中内存持续增长,最终可能耗尽堆内存)。

1. 函数原型

void free(void* ptr);
  • 参数 void* ptr:指向待释放内存空间的指针,该指针必须是 malloccalloc 或 realloc 的返回值(不可是栈内存指针、全局内存指针,否则会导致 “未定义行为”,如程序崩溃)。
  • 返回值:无(void)。

2. 核心特性

  • 仅释放内存,不改变指针值free 释放 ptr 指向的内存后,ptr 本身仍存储原内存地址(变为 “野指针”,指向已无效的内存),需手动将 ptr 设为 NULL,避免后续误操作野指针。
  • 不能重复释放:同一内存空间不能被 free 多次(重复释放会导致 “双重释放错误”,触发程序崩溃)。
  • 释放 NULL 无效果:若 ptr 为 NULLfree(NULL) 不会执行任何操作,因此建议释放后将指针置为 NULL,避免重复释放风险。

2.3 malloc和free的使用

#include<stdio.h>
#include<stdlib.h>int main()
{int n = 0;printf("请输入你所要申请的个数:");scanf("%d", &n);//输入个数//申请一块空间,用来存放数字int* p = (int*)malloc(n * sizeof(int));if (p == NULL)//判断{perror("malloc");return 1;}//使用内存空间for (int i = 0;i < n;i++){p[i] = i + 1;printf("%d ", p[i]);}free(p);//用完之后及时释放p = NULL;//及时置为空指针1,防止变为野指针return 0;
}

三 calloc 与 realloc:C 语言动态内存管理的扩展函数

在 C 语言中,calloc 和 realloc 是 <stdlib.h> 库中基于 malloc 扩展的动态内存函数,分别侧重 “初始化内存分配” 和 “内存大小调整”,与 free 配合使用,覆盖更丰富的动态内存管理场景。

3.1 calloc 函数:初始化的内存分配

calloc(contiguous allocation,连续分配)的核心作用是在堆内存中申请指定数量、指定大小的连续空间,并将所有字节初始化为 0,解决了 malloc 分配内存后残留 “垃圾值” 的问题。

3.1.1 函数原型

void* calloc(size_t nmemb, size_t size);
  • 参数解析
    • size_t nmemb:需分配的 “元素个数”(如 10 个 int 元素,nmemb=10);
    • size_t size:单个元素的字节数(如 int 类型为 sizeof(int));
    • 总申请内存字节数 = nmemb * size(与 malloc(nmemb * size) 申请的空间大小一致,但初始化行为不同)。
  • 返回值
    • 成功:返回指向初始化后内存空间的通用指针(void*),需强制转换为具体数据类型;
    • 失败:返回 NULL(因堆内存不足,需检查返回值避免空指针访问)。

3.1.2. 核心特性

  • 自动初始化内存为 0:分配的每个字节都会被设为 0(区别于 malloc 的 “随机垃圾值”),适合需要 “零初始” 的场景(如数组初始化、结构体成员默认值为 0)。
  • 连续内存分配:与 malloc 一致,分配的内存是连续的,满足数组、结构体等连续存储需求。
  • 生命周期可控:内存来自堆区,需用 free 手动释放,否则会导致内存泄漏。

3.2 realloc 函数:动态调整内存大小

realloc(re-allocation,重新分配)的核心作用是调整已通过 malloc/calloc/realloc 申请的内存空间大小,支持 “扩容” 或 “缩容”,是动态数据结构(如动态顺序表)实现 “容量自适应” 的关键函数。

3.2.1. 函数原型

void* realloc(void* ptr, size_t size);
  • 参数解析
    • void* ptr:指向已分配内存的指针(若为 NULLrealloc(NULL, size) 等价于 malloc(size),即新申请内存);
    • size_t size:调整后内存的总字节数(不是 “增加 / 减少的字节数”,如原内存为 10 字节,需扩至 20 字节,size=20)。
  • 返回值
    • 成功:返回指向调整后内存空间的新指针(可能与原 ptr 相同,也可能不同,取决于内存碎片情况);
    • 失败:返回 NULL,且原 ptr 指向的内存不会被释放(需避免因 realloc 失败导致原内存泄漏)。

3.2.2. 核心特性(内存调整逻辑)

realloc 调整内存时,会根据原内存的 “相邻空间是否足够” 选择两种策略:

  1. 原地调整(原地址复用):若原内存块后面有足够的连续空闲空间,直接在原地址后扩展内存,返回原 ptr(效率高,无需拷贝数据);
  2. 异地调整(新地址分配):若原内存后空间不足,会在堆中找一块足够大的新连续空间,将原内存的数据拷贝到新空间,释放原内存,返回新地址(效率低,需拷贝数据,但保证内存连续)。
  • 注意:缩容时,realloc 会直接截断超出 size 的部分,截断的内存数据会丢失,且需确保 size 不小于已存储数据的实际需求(避免数据截断)。

3.2.3. 关键使用注意事项

  • 保护原内存realloc 失败时返回 NULL,原 ptr 仍有效,因此不能直接用 ptr = realloc(ptr, size)(若失败,ptr 会被赋值为 NULL,原内存地址丢失导致泄漏),需用临时指针接收返回值;
  • 避免缩容数据丢失:缩容时 size 需大于等于已存储数据的字节数(如存储 5 个 intsize 至少为 5*sizeof(int));
  • 释放仍用 free:调整后的内存仍需用 free 释放,且只需释放最终返回的新指针(原 ptr 若被异地调整,已自动释放,无需重复释放)

四 常见的动态内存错误

在 C 语言动态内存管理中,因对malloc/calloc/realloc/free的使用逻辑理解不透彻,易出现各类错误,这些错误可能导致程序崩溃、内存泄漏或数据损坏,以下是高频错误及原因分析:

4.1 对 NULL 指针的解引用操作

错误表现

程序运行时触发 “段错误(Segmentation Fault)”,或读取 / 写入无效内存导致数据异常。

错误原因

malloc/calloc/realloc申请内存失败时会返回NULL,若未检查返回值直接对其解引用(如*ptr = 10;),相当于访问地址为 0 的无效内存(NULL本质是地址 0 的宏定义),触发未定义行为。

错误代码示例

int* arr = (int*)malloc(10 * sizeof(int));
// 未检查arr是否为NULL,直接赋值
arr[0] = 5;  // 若malloc失败(arr=NULL),此处解引用NULL,程序崩溃

解决办法

强制检查返回值:申请内存后必须判断指针是否为NULL,若失败则打印错误并处理(如终止程序)。

int* arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) {perror("malloc failed");  // 打印错误原因(如“malloc failed: Not enough space”)exit(EXIT_FAILURE);       // 内存申请失败无法继续,终止程序
}
arr[0] = 5;  // 确认指针有效后再操作

4.2 动态内存的越界访问

错误表现

程序可能崩溃、数据被意外篡改(如其他变量值异常),或运行时无明显错误但隐藏内存隐患(“脏写”)。

错误原因

访问动态内存时,超出了malloc/calloc/realloc申请的内存范围(如申请 10 个int的空间,却访问第 11 个元素),破坏堆内存的 “内存块管理结构”(堆内存中除用户数据外,还存储块大小、下一块地址等元数据),导致后续malloc/free异常。

错误代码示例

int* arr = (int*)malloc(5 * sizeof(int));  // 申请5个int(20字节)
if (arr == NULL) { perror("malloc"); exit(1); }
// 越界访问第6个元素(索引5,超出0~4的合法范围)
arr[5] = 10;  // 破坏堆内存结构,后续free可能崩溃
free(arr);

解决办法

  • 明确动态内存的 “合法索引范围”(如申请n个元素,索引为0~n-1);
  • 遍历或操作时,用变量(如size)控制边界,避免硬编码索引导致越界。

4.3  对非动态内存使用 free 释放

错误表现

程序直接崩溃,或触发 “无效指针释放” 错误(如 Windows 下 “Debug Assertion Failed”,Linux 下 “free (): invalid pointer”)。

错误原因

free的设计目的是释放堆内存(仅malloc/calloc/realloc申请的内存),若对 “栈内存”(如局部变量、数组)或 “全局 / 静态内存” 使用free,会破坏非堆内存的存储结构,触发未定义行为。

错误代码示例

int main() {int a = 10;          // 栈内存(局部变量)int arr[5] = {0};    // 栈内存(局部数组)static int b = 20;   // 静态内存(全局区)free(&a);   // 错误:释放栈内存free(arr);  // 错误:释放栈内存free(&b);   // 错误:释放静态内存return 0;
}

解决办法

  • 明确内存类型:仅对malloc/calloc/realloc返回的指针使用free
  • 避免 “混用指针”:不将栈内存 / 静态内存的地址赋值给 “动态内存指针”,防止误释放。

4.4  动态内存的重复释放

错误表现

程序崩溃,错误信息通常为 “double free or corruption”(双重释放或内存损坏)。

错误原因

同一动态内存块被free多次:第一次free已将内存归还给系统,第二次free时,指针指向的是 “已无效的堆内存块”,系统无法识别该块,触发内存管理错误。

错误代码示例

int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL) { perror("malloc"); exit(1); }free(arr);  // 第一次释放,内存已归还给系统
// 未将arr置为NULL,后续误判指针有效,再次释放
free(arr);  // 错误:重复释放,程序崩溃

解决办法

  • 释放后将指针置为 NULLfree后立即赋值ptr = NULL,因free(NULL)无任何操作,可避免重复释放;
  • 统一管理释放逻辑:用条件判断(如if (ptr != NULL))确保释放前指针有效,避免在多个函数中重复释放同一指针。
free(arr);
arr = NULL;  // 关键:释放后置空
// 后续即使误写free(arr),也不会触发错误
if (arr != NULL) {free(arr);  // 因arr=NULL,此句不执行
}

4.5  动态内存泄漏

错误表现

程序运行时内存占用持续增长(通过任务管理器 /top命令可观察),长期运行后可能因堆内存耗尽导致 “内存分配失败”(malloc返回NULL)。

错误原因

动态内存申请后,未用free释放,且指向该内存的指针 “丢失”(如指针被重新赋值、超出作用域),导致系统无法回收该内存,这部分内存成为 “泄漏内存”,无法被程序或其他进程复用。

错误代码示例

void func() {int* arr = (int*)malloc(10 * sizeof(int));  // 申请堆内存if (arr == NULL) { perror("malloc"); exit(1); }// 未free(arr),函数结束后arr超出作用域(指针丢失),内存泄漏
}int main() {while (1) {func();  // 循环调用,每次泄漏40字节(10个int),内存持续增长}return 0;
}

解决办法

  • “申请 - 释放” 配对:确保每一次malloc/calloc/realloc都有对应的free,尤其在函数返回、分支语句(if/else)中,避免遗漏释放;
  • 使用 “资源管理函数”:对复杂结构(如动态顺序表),封装专门的 “销毁函数”(如SeqListDestroy),统一释放所有动态内存;
  • 工具检测:使用内存泄漏检测工具(如 Valgrind、Visual Studio 的 “内存诊断”),定位未释放的内存块。

4.6  动态内存释放后继续使用(野指针操作)

错误表现

程序可能打印随机值、修改无效内存导致数据混乱,或偶发崩溃(取决于内存是否被系统重新分配)。

错误原因

free释放动态内存后,指针本身仍存储原内存地址(变为 “野指针”),此时该内存已归还给系统,可能被其他代码重新分配并写入数据;若继续通过野指针访问,会干扰其他数据,触发未定义行为。

错误代码示例

int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL) { perror("malloc"); exit(1); }free(arr);  // 释放后,arr变为野指针(指向已无效的内存)
// 继续使用野指针,访问无效内存
printf("%d\n", arr[0]);  // 打印随机值,或干扰其他内存
arr[1] = 20;             // 破坏系统分配给其他变量的内存

解决办法

  • 释放后立即置空free(ptr)后必须赋值ptr = NULL,将野指针转为 “安全的 NULL 指针”;
  • 访问前检查指针有效性:使用指针前,通过if (ptr != NULL)判断是否有效,避免操作野指针。

总结:动态内存错误的核心规避原则

  1. 申请必检查malloc/calloc/realloc后,强制判断返回值是否为NULL
  2. 访问不越界:明确动态内存的合法范围,用边界变量控制访问;
  3. 释放要合法:仅释放堆内存,不释放栈 / 静态内存,避免重复释放;
  4. 指针不野化free后立即置NULL,使用前检查指针有效性;
  5. 配对要严格:确保 “申请 - 释放” 一一对应,避免内存泄漏。

这些原则是避免动态内存错误的关键,尤其在实现动态顺序表、链表等数据结构时,需时刻遵循以保证程序稳定性。

五.动态内存经典笔试题分析

5.1  题目1

#include<stdio.h>
#include<stdlib.h>
#include<string.h>void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
int main()
{Test();return 0;
}

本质问题:函数参数传递方式错误(值传递无法修改实参指针)

C 语言中函数参数默认是值传递—— 即函数接收的是实参的 “副本”,对副本的修改不会影响原实参本身。

  • Test函数中,str是一个指向NULL的指针(实参);
  • 调用GetMemory(str)时,GetMemory的参数pstr副本指针(二者指向同一地址NULL,但内存空间独立);
  • GetMemory内部,p = (char*)malloc(100)仅修改了 “副本指针p” 的指向(让p指向新申请的 100 字节堆内存),但原实参str仍指向NULL(未被修改)。

此时Test函数中的str依然是NULL,后续strcpy(str, "hello world")相当于对NULL指针解引用,触发 “段错误(Segmentation Fault)”。

衍生问题:内存泄漏(若忽略空指针错误)

更正后正确代码如下: 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>// 参数改为二级指针:接收指针str的地址
void GetMemory(char** p)
{// 给一级指针str分配内存(通过二级指针p间接访问str)*p = (char*)malloc(100);// 检查malloc是否成功(避免分配失败导致后续错误)if (*p == NULL) {perror("malloc failed");exit(EXIT_FAILURE);}
}void Test(void)
{char* str = NULL;GetMemory(&str);  // 传递str的地址(二级指针)strcpy(str, "hello world");printf("%s\n", str);  // 规范使用printffree(str);  // 释放动态内存,避免泄漏str = NULL; // 释放后置空,避免野指针
}int main()
{Test();return 0;
}

5.2  题目2

#include<stdio.h>
#include<stdlib.h>
#include<string.h>char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

核心错误:返回栈区局部变量的地址(野指针)

  1. 内存特性GetMemory函数中的数组p[]栈区局部变量,其生命周期仅限于GetMemory函数内部 —— 当函数执行结束时,栈区会自动释放p占用的内存,该内存地址不再受程序控制(可能被其他函数的局部变量覆盖)。

  2. 指针失效

    • GetMemory中,return p返回的是数组p的首地址(栈区地址);
    • 但当GetMemory执行完毕后,p的内存已被释放,此时Test函数中的str接收的是一个指向无效栈内存的野指针
  3. 未定义行为printf(str)通过野指针访问已释放的栈内存,结果是不可预测的 —— 可能打印乱码、空内容,或程序崩溃(取决于该内存是否被其他数据覆盖)。

更正后正确代码如下: 

char* GetMemory(void)
{// 静态变量存储在全局区,函数结束后不释放static char p[] = "hello world"; return p;
}void Test(void)
{char* str = NULL;str = GetMemory();printf("%s\n", str); // 可正确访问(静态变量未释放)
}

5.3  题目3

#include<stdio.h>
#include<stdlib.h>
#include<string.h>void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

核心错误:内存泄漏

malloc申请的 100 字节堆内存没有对应的free释放

  • 动态内存(堆区)的生命周期由程序员控制,malloc后必须用free释放,否则会导致内存泄漏;
  • 此代码中,Test函数执行完毕后,str作为局部变量被销毁,指向堆内存的指针丢失,这 100 字节内存无法被回收,成为 “无主内存”,长期运行会逐渐耗尽系统堆内存。

更正后正确代码如下: 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>void GetMemory(char** p, int num)
{*p = (char*)malloc(num);// 增加malloc失败检查,避免空指针后续操作if (*p == NULL) {perror("malloc failed");exit(EXIT_FAILURE);}
}void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf("%s\n", str); // 规范printf用法,指定格式符free(str); // 释放动态内存,避免泄漏str = NULL; // 释放后置空,避免野指针
}int main()
{Test();return 0;
}

5.4  题目4

#include<stdio.h>
#include<stdlib.h>
#include<string.h>void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

核心问题:释放后使用野指针

  1. 内存释放后指针状态

    • free(str) 释放了 str 指向的 100 字节堆内存,此时该内存已归还给系统,不再受程序控制(可能被其他代码分配和修改)。
    • 但 free 不会改变 str 本身的值(指针仍存储原内存地址),str 成为野指针(指向无效内存的指针)。
  2. 错误的判断与操作

    • if (str != NULL) 条件永远为真(因为 free 未改变 str 的值,它仍指向原地址而非 NULL)。
    • 进入分支后,strcpy(str, "world") 通过野指针写入已释放的内存,这会:
      • 破坏系统堆内存管理结构(导致后续 malloc/free 异常);
      • 若该内存已被其他程序分配,会篡改其他数据,引发不可预测的错误(如程序崩溃、数据错乱)。

执行结果的不确定性

  • 可能 “看似正常” 输出 world:若释放的内存未被立即复用,写入和打印可能暂时成功,但这是巧合。
  • 可能崩溃或打印乱码:若内存已被系统回收或分配给其他变量,操作会触发 “段错误” 或覆盖有效数据。

更正后正确代码如下: 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>void Test(void)
{char* str = (char*)malloc(100);// 检查malloc是否成功(增加健壮性)if (str == NULL) {perror("malloc failed");return;}strcpy(str, "hello");free(str);        // 释放内存str = NULL;       // 关键:释放后立即置空,避免野指针if (str != NULL)  // 此时条件为假,不会执行危险操作{strcpy(str, "world");printf("%s\n", str);  // 规范printf用法}
}int main()
{Test();return 0;
}

六.  C/C++中程序内存区域划分

程序加载到内存后,内存空间从低地址到高地址大致分为以下几个区域(不同系统 / 编译器可能有细微差异,但核心结构一致):

1. 代码区(Text Segment)

  • 存放内容:编译后的二进制机器指令(函数执行代码)。

  • 特点:只读(防止意外修改)、可共享(多进程共享同一份代码)、大小编译时固定。

2. 常量存储区

  • 存放内容:字符串常量(如"hello")、const修饰的全局常量。

  • 特点:只读(修改会崩溃)、生命周期与程序一致(随程序启动到结束)。

3. 全局 / 静态存储区

  • 存放内容:全局变量、static修饰的静态变量(包括全局静态和局部静态)。

    • 已初始化的存于.data段,未初始化或初始化为 0 的存于.bss段。

  • 特点:生命周期与程序一致、系统自动分配释放、编译时确定大小。

4. 堆区(Heap)

  • 存放内容:动态分配的内存(malloc/new申请的空间)。

  • 特点:大小动态变化(向上生长)、需手动free/delete释放、可能碎片化、生命周期由程序员控制。

5. 栈区(Stack)

  • 存放内容:局部变量、函数参数、返回地址等。

  • 特点:自动分配释放(随作用域)、遵循先进后出原则、大小固定(通常几 MB)、分配效率极高、超出会栈溢出。

http://www.dtcms.com/a/356130.html

相关文章:

  • 同类软件对比(二):VS Code 与 PyCharm 的 Python 开发对比与使用建议
  • JavaScript初识:给小白的第一堂编程课
  • Day20 常见降维算法
  • 沙箱操作工具
  • 机器学习(讲解)
  • ROS2 入门实战 —— 从环境搭建到第一次控制小乌龟(一)
  • 【电子设计自动化(EDA)】Altium Designer25——电子设计自动化(EDA)软件版保姆级下载安装详细图文教程(附安装包)
  • linux网络编程-----TCP服务端并发模型(epoll)
  • [数组]27.移除元素
  • SQLServer日志文件损坏恢复办法
  • day13(练习题)
  • 卷积核尺寸如何计算?
  • Containerd卸载指南
  • shell脚本编程规范与变量
  • Shell 入门
  • LeetCode刷题记录----35.搜索插入位置(Easy)
  • 117、【OS】【Nuttx】【周边】效果呈现方案解析:while 循环处理(下)
  • 虚拟机逃逸攻防演练技术文章大纲
  • 八个按键一一对应八个输出
  • C语言————斐波那契数列(例题1)
  • BoardSim仿真
  • DoIP路由激活报文
  • Shell脚本(2)
  • 洛谷p1028数的计算 详解
  • 【智能体】零代码学习 Coze 智能体(1)
  • 人工智能基础概念
  • java通过redis简单实现分布式锁
  • 【MySQL数据库】存储引擎 学习记录
  • 深度学习进阶
  • B站 XMCVE Pwn入门课程学习笔记(8)