【C语言进阶】动态内存与柔性数组:C语言开发者必须知道的陷阱与技巧

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C语言 “ 登神长阶 ”
🤡往期回顾🤡:C语言动态内存管理
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

❀C语言动态内存管理

  • 📒1. 常见的动态内存错误
    • 🏞️对NULL指针的解引用操作
    • ⛰️对动态开辟空间的越界访问
    • 🌄对非动态开辟内存使用free释放
    • 🍁使用free释放一块动态开辟内存的一部分
    • 🍂对同一块动态内存多次释放
    • 🌸动态开辟内存忘记释放(内存泄漏)
  • 📚2. 动态内存实战测试
  • 📜3. 柔性数组
    • 🌞特点
    • 🌙使用
    • ⭐优势
  • 📖4. 总结


前言:在C语言的广阔天地中,动态内存管理是一把双刃剑,它既为开发者提供了极大的灵活性和效率,也暗藏着诸多陷阱与挑战。作为C语言编程的基石之一,动态内存分配(如malloc、calloc、realloc等函数的使用)几乎贯穿于每一个复杂程序的设计与实现之中。然而,不恰当的内存管理实践往往会导致内存泄露、越界访问、重复释放等严重问题,进而影响程序的稳定性和安全性

柔性数组(也称为可变长数组或末尾数组)作为C99标准引入的一项特性,为开发者提供了一种在结构体中存储未知大小数据的有效方式。这一特性在处理字符串、动态数组等场景时尤为有用,但同样需要谨慎使用,以避免因误解其工作原理而引入新的问题

本文旨在深入探讨C语言中常见的动态内存错误及其成因,通过实例分析帮助读者理解这些错误的本质,并提供实用的解决方案。同时,本文还将详细介绍柔性数组的概念、工作原理及其在C语言编程中的应用,揭示其背后的设计哲学和潜在陷阱

让我们一同踏上这段探索之旅,揭开C语言动态内存管理与柔性数组的神秘面纱!


📒1. 常见的动态内存错误

在C语言中,动态内存分配是常见且强大的功能,但同时也容易引发各种错误,下面让我们来了解一下这些错误


🏞️对NULL指针的解引用操作

  • 错误描述: 当使用malloc、realloc或calloc等函数动态分配内存时,如果分配失败,这些函数会返回NULL指针。如果不对返回的指针进行检查,直接对其进行解引用操作,将会导致程序崩溃

错误代码示例 (C语言):

#define INT_MAX 0x3f3f3f3f
void test()
{
	int* p = (int*)malloc(INT_MAX * 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

在这里插入图片描述


  • 解决方案: 在每次动态分配内存后,都应该检查返回的指针是否为NULL。如果是NULL,则表明内存分配失败,应进行相应的错误处理

解决方案示例 (C语言):

#define INT_MAX 0x3f3f3f3f
void test()
{
	int* p = (int*)malloc(INT_MAX * 4);
	if (p == NULL)
	{
		perror("malloc fail");
	}
	else
	{
		*p = 20;
	}
	free(p);
}

⛰️对动态开辟空间的越界访问

  • 错误描述: 在动态分配的内存区域之外进行读写操作,即越界访问。这会导致未定义行为,可能破坏程序的稳定性和安全性

错误代码示例 (C语言):

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(0);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

在这里插入图片描述

  • 解决方案: 确保对动态分配的内存进行访问时,不要超出其分配的范围。可以通过设置合理的循环条件或使用数组索引来避免越界

解决方案示例 (C语言):

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(3);
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i; //当i是10的时候越界访问,所以不要超出最大范围
	}
	free(p);
}

🌄对非动态开辟内存使用free释放

  • 错误描述: 尝试使用free函数释放非动态分配的内存,如栈上分配的内存或全局/静态变量。这会导致未定义行为,因为free函数只适用于通过malloc、realloc或calloc等函数动态分配的内存

错误代码示例 (C语言):

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
}

在这里插入图片描述

  • 解决方案: 确保只使用free函数释放动态分配的内存。对于栈上分配的内存或全局/静态变量,不需要也不应该使用free函数进行释放

解决方案示例 (C语言):

void test()
{
	int a = 10;
	int* p = &a;
}

🍁使用free释放一块动态开辟内存的一部分

  • 错误描述: 在动态分配的内存块中,只对其中一部分进行访问后,就尝试使用free函数释放整个内存块。然而,如果在访问过程中修改了指向内存块起始位置的指针,那么free函数将无法正确释放整个内存块

错误代码示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}
  • 解决方案: 在调用free函数之前,确保指针仍然指向动态分配的内存块的起始位置。如果需要在内存块中移动指针,可以在调用free之前将指针重新指向起始位置,或者避免在需要释放内存之前修改指针

解决方案示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	int* a = p;
	p++;
	free(a);
}

🍂对同一块动态内存多次释放

  • 错误描述: 对同一块动态分配的内存进行多次free操作。这会导致未定义行为,因为一旦内存被释放,其对应的指针就变成了悬空指针(dangling pointer),再次对悬空指针进行free操作是危险的

错误代码示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}
  • 解决方案: 确保每块动态分配的内存只被释放一次。在释放内存后,将指针置为NULL,以避免再次对其进行释放操作

解决方案示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	p = NULL;
}

🌸动态开辟内存忘记释放(内存泄漏)

  • 错误描述: 在程序中动态分配了内存,但在不再需要这些内存时忘记了释放它们。这会导致内存泄漏,即程序占用的内存量不断增加,最终可能导致系统资源耗尽

解决方案示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
  • 解决方案: 在程序中及时释放不再需要的动态分配的内存。可以通过在适当的位置调用free函数来实现。同时,也要注意在程序结束前释放所有动态分配的内存,以避免内存泄漏

解决方案示例 (C语言):

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	free(p);
}

切记:动态开辟的空间一定要释放,并且正确释放


📚2. 动态内存实战测试

动态内存实战测试是确保你的C语言程序在处理动态内存时既安全又高效的重要手段,现在让我来带领你们巩固动态内存知识


请问运行Test 函数会有什么样的结果?

题目1:

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

结果:程序崩溃,因为 str 是 NULL

存在问题:

  • 指针传递问题:
    GetMemory 函数中,p 是一个指向 char 的指针的局部变量。当你执行 p = (char *)malloc(100); 时,你实际上是在为 p 分配了一个新的内存地址,但这个新地址仅对 GetMemory 函数内的 p 指针有效。一旦GetMemory 函数返回,这个新的内存地址就会丢失,因为 GetMemory 函数是通过值传递接收的 str 指针(即 str 的一个拷贝),而 str 本身在 Test 函数中并未被修改
  • 内存泄漏:
    由于 GetMemory 中的 p 指针在函数返回后被销毁,但它指向的内存并没有被释放(即没有调用 free),这会导致内存泄漏
  • 未定义行为:
    在 Test 函数中,strcpy(str, “hello world”); 尝试将字符串 “hello world” 复制到 str 指向的地址。但由于 str 在 GetMemory 函数调用后仍然是 NULL,这个操作会尝试写入一个空指针,导致未定义行为

修改后代码 (C语言):

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

void GetMemory(char** p) 
{
    *p = (char*)malloc(100);
}

void Test(void) {
    char* str = NULL;
    GetMemory(&str);
    if (str != NULL)
    {
        strcpy(str, "hello world");
        printf(str);
        free(str); // 释放分配的内存  
    }
}

题目2:

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

结果:程序崩溃,因为 p在出了GetMemory函数之后,p占用的内存会自己释放,str就不确定了

存在问题:

作用域:

  • 局部数组 p 的生命周期仅限于 GetMemory 函数的执行期间。一旦 GetMemory 函数返回,p 数组所占用的内存就会被释放(在栈上),因此返回的指针将指向一个不再有效的内存区域

修改后代码 (C语言):

#include <stdlib.h>  

char* GetMemory(void) {  
    // 使用 malloc 分配足够的内存来存储 "hello world" 字符串和结尾的空字符 '\0'  
    char* p = (char*)malloc(12); // "hello world" 加上 '\0' 共计 12 个字符  
    if (p != NULL) 
    {  
        strcpy(p, "hello world"); // 将 "hello world" 复制到新分配的内存中  
    }  
    return p;  
}  
  
void Test(void) {  
    char* str = GetMemory();  
    if (str != NULL) 
    {  
        printf(str); // 正确使用 printf 格式化字符串  
        free(str); // 释放之前分配的内存  
    }  
}

题目3:

#include <stdlib.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);
}

结果:程序虽然能正常运行,当时存在内存泄漏的问题

存在问题:

  • 由于未释放分配的内存,还存在内存泄漏的问题,应该在不再需要分配的内存时,使用 free 函数来释放它

修改后代码 (C语言):

#include <stdlib.h>  

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
  
void Test(void)  
{  
	char* str = NULL;  
	GetMemory(&str, 100);  
	if (str != NULL) 
	{  
		strcpy(str, "hello");  
		printf(str);  
		free(str); // 释放内存  
		str = NULL; // 防止野指针  
	}  
}

题目4:

#include <stdlib.h>  

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

结果:程序崩溃

存在问题:

  • 未定义行为:
    当执行 free(str); 后,str 指针的值(即内存地址)本身并没有改变,但它现在指向的内存块已经不再是您的程序可以安全访问的

修改后代码 (C语言):

#include <stdlib.h>  

void Test(void)  
{  
	char* str = (char*)malloc(100);  
	if (str != NULL) {  
		strcpy(str, "hello");  
		printf("%s\n", str);  
		free(str);  
		str = NULL; // 防止野指针,但此时不应再使用str  
	}  
	// 注意:不要在这里或之后尝试使用str,因为它已经指向了无效的内存,
	// 如果想继续使用就必须重新分配内存  
}

📜3. 柔性数组

柔性数组(Flexible Array)是C语言中一种特殊的数据结构,它允许在结构体中定义一个长度可变的数组。这种技术为程序员提供了更灵活的内存管理方式,特别适用于那些需要在运行时确定数组大小的情况

定义与原理:

  • 柔性数组通常是在结构体的最后一个成员位置声明一个长度为0的数组(或称为柔性数组成员)。尽管数组的长度被声明为0,但它实际上并不占用任何内存空间,因为数组名本身不占空间,它只是一个偏移量。然而,这个数组的存在允许我们在结构体之后紧接着分配一块连续的内存区域,用于存储数组的实际数据。这样,结构体和数组就形成了一个连续的内存块,便于管理和释放

🌞特点

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof 返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

代码示例 (C++):

typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

🌙使用

代码示例 (C++):

typedef struct pxt
{
	int num;
	int a[0];//柔性数组成员
}pxt;

int main()
{
	int i = 0;
	pxt* p = (pxt*)malloc(sizeof(pxt) + 100 * sizeof(int));
	//业务处理
	p->num = 100;
	for (i = 0; i < 100; i++)
	{
		p->a[i] = i;
	}
	for (i = 0; i < 100; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);
	return 0;
}

这样柔性数组成员a,相当于获得了100个整型元素的连续空间


⭐优势

柔性数组也可以使用一下方法完成上面的业务,但是上面的方法优于下面这种,上述只需要做一次free就可以释放所有的内存,我们以学习的目的了解一下第二种方式

typedef struct pxt
{
	int num;
	int *p_a;//柔性数组成员
}pxt;

int main()
{
	int i = 0;
	pxt* p = (pxt*)malloc(sizeof(pxt) + 100 * sizeof(int));
	//业务处理
	p->num = 100;
	p->p_a = (pxt*)malloc(p->num * sizeof(int));
	 
	for (i = 0; i < 100; i++)
	{
		p->p_a[i] = i;
	}
	for (i = 0; i < 100; i++)
	{
		printf("%d ", p->p_a[i]);
	}

	free(p->p_a);
	p->p_a = NULL;
	free(p);
	p = NULL;

	return 0;
}

柔性数组的优点:

  • 灵活性: 允许在运行时动态确定数组的大小,满足不同的数据存储需求
  • 内存管理方便: 由于结构体和数组是连续分配的,因此可以一次性申请和释放内存,减少了内存碎片化的风险,提高了内存管理的效率
  • 设计简约: 简化了代码结构,提高了程序的可读性和可维护性

📖4. 总结

在深入探讨了C语言中常见的动态内存错误及柔性数组的应用后,我们不难发现,动态内存管理是C语言编程中不可或缺但又极具挑战性的一部分。它要求开发者不仅要有扎实的编程基础,还需要具备严谨的逻辑思维和细致入微的调试能力

我们了解了内存泄露、野指针、重复释放等动态内存错误的成因及防范策略,这些错误看似简单,实则可能对程序的稳定性和安全性造成严重影响。因此,在日常编程中,我们必须时刻保持警惕,遵循最佳实践,确保每一块分配的内存都能得到妥善管理

同时,柔性数组作为C99标准引入的一项实用特性,为我们提供了一种在结构体中灵活存储未知大小数据的方法。然而,柔性数组的使用也需谨慎,必须明确其工作原理和限制条件,避免误用或滥用导致的问题

总的来说,C语言的动态内存管理和柔性数组是相辅相成的两个概念。它们为开发者提供了强大的工具来构建高效、灵活的程序,但同时也要求开发者具备高度的责任感和严谨性。希望本文能够为读者在学习C语言动态内存管理和柔性数组的过程中提供一些有益的参考和启示,帮助大家更好地掌握这些关键技能,编写出更加稳定、安全、高效的C语言程序。让我们在未来的编程道路上继续探索、学习、进步!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/879242.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ 基础入门

文章内容是学习过程中的知识总结&#xff0c;如有纰漏&#xff0c;欢迎指正 文章目录 前言 1. 重要概念 1.1 Publisher 1.2 Message 1.3 Exchange 1.4 BindingKey 1.5 Routingkey 1.6 Queue 1.7 Consumer 1.8 Connection 1.9 Channel 1.10 Virtual Host 1.11Broker 2. RabbitMQ…

三菱变频器以模拟量电流进行频率设定(电流输入)

POINT 1、在 STF(STR)信号 ON 时&#xff0c;发出启动指令。2、请将 AU 信号置为 ON。 3、请设定 Pr.79 运行模式选择 “2”(外部运行模式)。 接线示例 重点&#xff1a;请将 AU 信号置为 ON。 操作示例&#xff1a;以 60Hz 运行。 1、接通电源时的画面&#xff0c;监视器显…

vue3+ant design vue 中弹窗自定义按钮设置及以冒号为基准布局

1、自定义弹窗按钮&#xff0c;去除取消和确定按钮。&#xff08;网上很多方法都是说通过插槽来实现&#xff0c;但是试了下不生效&#xff0c;那既然插槽不生效的话&#xff0c;干脆直接写按钮就好了&#xff09; <a-modalv-model:open"open"title"人员信息…

如何挑选适用的WMS 智能仓储管理系统?这份盘点攻略请收好!

本文将盘点十款WMS智能仓储管理系统&#xff0c;为企业选型提供参考&#xff01; 在现代企业的物流运作中&#xff0c;仓库就如同一个关键的枢纽&#xff0c;连接着生产与销售的各个环节。而一个高效的 WMS 智能仓储管理系统&#xff0c;就像是一位精明的管家&#xff0c;能让仓…

选购到不好的宠物空气净化器会有什么危害?有哪几款推荐

前三个月真的是被我男朋友气到了&#xff0c;明明说好的一起养猫&#xff0c;他又嫌这嫌那的&#xff0c;真的是无语住。 在养猫前的一个月就说好了&#xff0c;谁下班早谁就先回家收拾&#xff0c;包括进门开窗通风、给猫喂食、还有铲猫砂盆。但是他现在抱怨说太麻烦了&#…

开源即时通讯IM框架MobileIMSDK的H5端技术概览

一、基本介绍 MobileIMSDK的H5端是一套纯JS编写的基于标准WebSocket的即时通讯库&#xff1a; 1&#xff09;超轻量级、极少依赖&#xff1b;2&#xff09;纯JS编写、高度提炼&#xff0c;简单易用&#xff1b;3&#xff09;基于标准WebSocket协议&#xff0c;客户端兼容性好…

JavaEE:网络初识

文章目录 网络初识网络中的重要概念IP地址端口号认识协议(最核心概念)OSI七层模型TCP/IP五层(或四层)网络模型网络设备所在分层封装和分用 网络初识 网络中的重要概念 网络互联的目的是进行网络通信,也是网络数据传输,更具体一点,是网络主机中的不同进程间,基于网络传输数据.…

cmd命令

常用命令 查看电脑名称&#xff1a; hostname 查看网卡信息&#xff1a; ipconfig 快速打开网络设置界面&#xff1a; control.exe netconnections 或 rundll32.exe shell32.dll,Control_RunDLL ncpa.cpld 打开防火墙设置&#xff1a; wf.msc 指定网卡设置IP地址&#…

如何使用ssm实现高校智能培训管理系统分析与设计+vue

TOC ssm633高校智能培训管理系统分析与设计vue 第一章 绪论 1.1 选题背景 目前整个社会发展的速度&#xff0c;严重依赖于互联网&#xff0c;如果没有了互联网的存在&#xff0c;市场可能会一蹶不振&#xff0c;严重影响经济的发展水平&#xff0c;影响人们的生活质量。计算…

如何使用宝塔面板安装中间件

如何快速安装中间件&#xff0c;宝塔镇河妖非常简单。 使用 SSH 连接工具&#xff0c;如堡塔SSH终端连接到您的 Linux 服务器后&#xff0c;挂载磁盘&#xff0c;根据系统执行相应命令开始安装&#xff08;大约2分钟完成面板安装&#xff09;&#xff1a; Centos安装脚本 yum…

C++:日期类的实现

目录 一、前言 二、头文件 三、各个函数的实现 打印、检查日期及获取日期 、、-、-、 、<、<、>、>、 &#xff01; 日期-日期 >>、<< 一、前言 前面几篇讲了关于类和对象的一些知识&#xff0c;本篇就来实现一下前面用到的日期类。 二、头文…

【LGR-200-Div.4】洛谷入门赛 #27 A - H题解,包含(C++, Go语言)

前言&#xff1a; 本文为【LGR-200-Div.4】洛谷入门赛 #27 A - H题解 我只是一个只会各种暴力法的蒟蒻&#xff0c;这场比赛没有参加&#xff0c;是比赛完去写的&#xff0c;但是那个题目昨天晚上才能提交&#xff0c;导致拖久了一点 最后面贴一个Go语言的&#xff0c;反正也没…

英文ai写作怎么写?5个软件帮助你轻松进行ai写作

英文ai写作怎么写&#xff1f;5个软件帮助你轻松进行ai写作 AI写作工具正在改变内容创作方式&#xff0c;尤其是英文写作。以下是5款优秀的AI写作工具&#xff0c;它们可以帮助你快速、高效地完成各种英文写作任务&#xff0c;无论是博客、文章、社交媒体文案还是电子邮件。 聪…

Internet选项检查所存网页的较新版本的设置

每次访问此页时检查代表着,你无论打开任何网页时,都不用IE缓存,直接刷新浏览每次启动IE时检查,代表着,只要你IE浏览器不关闭,,那么他在访问相当网站,网页时,就会调用IE缓存(你会感觉打开非常快),这时候,他并没有下载网页,,只是调用缓存而已。。但是如果你关闭IE浏览器,再重新打…

AI问答-HTTP:理解 Content-Disposition

本文背景 在下载arraybuffer文件时&#xff0c;想要获取文件名&#xff0c;这时引入本文内容Content-Disposition&#xff0c;我们在Content-Disposition获取到文件名就可以在下载后的文件以该文件名命名了。 一、简介 Content-Disposition是HTTP协议中的一个响应头字段&…

跨平台开发新视角:利用Android WebView实现Web内容的原生体验

在移动应用开发领域&#xff0c;跨平台解决方案一直是一个热门话题。开发者们不断寻求能够同时在iOS和Android平台上提供一致用户体验的方法。而Android的WebView组件&#xff0c;作为一个强大的工具&#xff0c;允许开发者在Android应用中嵌入Web内容&#xff0c;为用户提供接…

第R3/4周:天气预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前言 这周的任务添加了探索式数据分析&#xff08;EDA&#xff09;&#xff0c;什么是探索式数据分析呢&#xff1f; 探索性数据分析&#xff08;Explor…

linux 双网卡服务器突然断电后网卡单通故障解决

某台linux 双网卡服务器突然断电后网卡单通故障解决 故障现象&#xff1a;断电后重启服务器&#xff0c;主用网卡IP只能同网段访问&#xff0c;其他网段无法访问&#xff0c;备用网卡则正常&#xff1b; 解决方案&#xff1a;route -n查询路由信息&#xff0c;发现主网卡路由…

第二期: 第11节, uboot 命令的使用

问题&#xff1a;如果你只想控制一个led 灯&#xff0c;并且不想去写驱动。 那么可以直接 使用uboot 的命令&#xff0c;去改写内存。 uboot 命令的解析&#xff1a; 读命令&#xff0c; md[.b, .w. .l] address 注意&#xff1a; 这里的 .w 指的是两个字节&#xff0c;…

探索Python的Excel世界:openpyxl的魔法之旅

文章目录 探索Python的Excel世界&#xff1a;openpyxl的魔法之旅背景&#xff1a;为什么选择openpyxl&#xff1f;什么是openpyxl&#xff1f;如何安装openpyxl&#xff1f;简单的库函数使用方法场景应用&#xff1a;openpyxl在实际工作中的应用常见bug及解决方案总结 探索Pyth…