C语言详解(动态内存管理)2

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言

🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


目录

  • 前言
  • 1、常见动态内存错误
      • 1.1 对NULL指针的解引用操作
      • 1.2 对动态内存空间的越界访问
      • 1.3 对非动态开辟内存使用free释放
      • 1.4 使用free释放动态内存的一部分
      • 1.5 对同一快动态内存多次释放
      • 1.6 动态开辟内存忘记释放(内存泄漏)
  • 2、动态内存经典笔试题分析
      • 2.1 题目一
      • 2.2 题目二
      • 2.3 题目三
      • 2.4 题目四
  • 3、柔性数组
      • 3.1 什么是柔性数组
      • 3.2 柔性数组的特点
      • 3.3 柔性数组的使用
      • 3.4 柔性数组的优势
  • 总结

前言

总的来说,动态内存管理为我们提供了更加灵活、高效和可扩展的内存管理方式,但动态内存管理函数可能会带来一些风险,主要包括内存泄漏、内存溢出和野指针等问题,我们在使用动态内存管理函数时要多留心,避免风险的出现


1、常见动态内存错误

1.1 对NULL指针的解引用操作

如果我们写的代码不严谨,没有考虑到动态内存分配失败的可能,就会写出类似于下面的代码:

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	
	//直接使用指针p
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	return 0;
}

这样的代码可能并没有什么问题,但是存在很大的隐患,因为动态内存函数是有可能开辟内存空间失败的,当开辟失败时会返回NULL,而NULL指针是不能解引用的
像VS这样比较强大的编译器会立马检测到并提示你
在这里插入图片描述

为了避免这种错误,我们需要对指针p进行判断,再决定是否使用

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//判断p是否为空指针
	if (p == NULL)
	{
		//打印出错误信息
		perror("malloc");
		//终止程序
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	return 0;
}

1.2 对动态内存空间的越界访问

我们用动态内存函数开辟多大的空间,我们就使用多大的空间,不能越界访问,例如:

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//判断p是否为空指针
	if (p == NULL)
	{
		//打印出错误信息
		perror("malloc");
		//终止程序
		return 1;
	}
	int i = 0;
	//p+1跳过1个整型,p+10就会越界
	for (i = 0; i <= 10; i++)
	{
		p[i] = i + 1;
	}
	return 0;
}

聪明的VS也会检测出错误提示你
在这里插入图片描述


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

free函数是用来释放由动态内存函数开辟的空间的,不能释放普通内存

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

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	free(p);
	p = NULL;
	return 0;
}

当我们运行起来后就出问题了
在这里插入图片描述


1.4 使用free释放动态内存的一部分

上面我们用malloc函数申请了10个整型空间,然后通过for循环给这10个整型空间内放1~10的整数,有些同学可能会为了方便这样写代码:

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//判断p是否为空指针
	if (p == NULL)
	{
		//打印出错误信息
		perror("malloc");
		//终止程序
		return 1;
	}
	//给申请的动态空间内存1~10
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p++ = i;
	}
	//释放动态内存空间
	free(p);
	p = NULLreturn 0;
}

当我们运行起来才发现写出了BUG
在这里插入图片描述
这又是为什么呢?
事实上此时free(p)中的p指针已经不再指向malloc开辟的动态内存的起始地址了,因为*p++这里对p的指向不断递增

free操作的指针必须指向要被释放的动态内存的起始地址


1.5 对同一快动态内存多次释放

当我们用完一块动态内存空间后不再使用对其释放后,可能会因为忘记而重复释放一次,并且如果第一次释放时忘记给p指针赋NULL,那么程序就会出错

	//使用...
	
	//释放动态空间
	free(p);

	//...

	free(p);
	p = NULL;
	return 0;

但是如果我们两次释放时都给p指针赋了NULL,那基本不会发生什么事,相当于没有错,只是逻辑上讲不通
所以,在我们用free释放完动态内存空间后,紧跟着对指针赋NULL是很有必要的


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

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

当我们写代码的时候,存在这样一种可能会出现的错误,那就是动态开辟的内存忘记释放或者因为某些原因还没有到free语句就提前终止代码,这里举个简单的例子

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

void text()
{
	int flag = 1;
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 1;
	}

	//使用
	
	//因为某些原因函数提前返回了
	if (flag == 1)
	{
		return;
	}

	//free函数
	free(p);
	p = NULL;
}

int main()
{
	//自定义函数
	text();

	//后面还有大量代码
	//....

	return 0;
}

虽然我们确实用了free函数释放空间,但是当代码量较大时可能会因为某些原因还没到free函数就提前终止了,而我们还没意识到,就算后面我们意识到了这个问题这块内存我们也找不到了
只有整个程序结束后这块内存才能被释放,如果程序一直不结束这块空间就再也找不到了,这就叫内存泄漏

所以,就算动态内存申请使用后用了free,也是有可能犯内存泄漏的错误,我们要多加小心

内存泄漏是比较可怕的,尤其是某些24小时不断运行的服务器程序,如果存在内存泄漏,内存被耗干也只是时间的问题


2、动态内存经典笔试题分析

2.1 题目一

请问运行下面 text函数会有什么后果?

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

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

void text(void)
{
	char* str = NULL;
	get_memory(str);
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	text();
	return 0;
}

上面的代码一共有两个问题
第一个问题:malloc申请动态内存空间后没有使用free函数释放,这可能会导致内存泄漏

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

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

void text(void)
{
	char* str = NULL;
	get_memory(str);
	strcpy(str, "hello world");
	printf(str);

	free(str);
	str = NULL;
}

int main()
{
	text();
	return 0;
}

第二个问题: 函数传参传值调用和传址调用使用错误

这个代码的意思是申请一块动态内存空间地址交给指针p,通过指针p再交给指针str,再使用strcpy函数将字符串拷贝到动态内存空间内,最后打印出字符串
但是get_memory函数传参的时候使用的是传值调用,所以指针p跟指针str没有关系

有两种纠错方法
方法一: 将传值调用改为传址调用,此时p为二级指针

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

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

void text(void)
{
	char* str = NULL;
	get_memory(&str);
	strcpy(str, "hello world");
	printf(str);

	free(str);
	str = NULL;
}

int main()
{
	text();
	return 0;
}

在这里插入图片描述

方法二: 直接返回指针p的地址,不需要传参

char* get_memory()
{
	char* p = (char*)malloc(100);
	return p;
}

void text(void)
{
	char* str = NULL;
	str = get_memory();
	strcpy(str, "hello world");
	printf(str);

	free(str);
	str = NULL;
}

int main()
{
	text();
	return 0;
}

在这里插入图片描述


2.2 题目二

请问运行下面 text函数会有什么后果?

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

char* get_memory(void)
{
	char p[] = "hello world";
	return p;
}

void text(void)
{
	char* str = NULL;
	str = get_memory();
	printf(str);
}

int main()
{
	text();
	return 0;
}

上面的代码是一个非常经典的例子,之前在C语言(指针)3中野指针一小节介绍过类似的例子

上面代码的问题:
我们在自定义函数get_memory中创建了一个局部临时数组存入字符串“hello world”,再将字符串的首地址返回用指针str接收,虽然此时指针str确实指向字符串“hello world”的首地址,但是此时str是没有权限访问这块空间的

因为在局部数组p在出了get_memory函数后就销毁了,它申请的空间会被收回,即使指针str能找到这块空间,但是它已经没有权限使用了,此时str就是一个野指针

在这里插入图片描述

所以我们应该避免返回栈空间地址

想要改正上面的代码也很简单,我们申请一块动态内存就行,同时也别忘了释放

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

char* get_memory(void)
{
	char* p = (char*)malloc(20);
	strcpy(p, "hello world");
	return p;
}

void text(void)
{
	char* str = NULL;
	str = get_memory();
	printf(str);
	
	free(str);
	str = NULL;
}

int main()
{
	text();
	return 0;
}

2.3 题目三

请问运行下面 text函数会有什么后果?

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

void get_memory(char** p, size_t num)
{
	*p = (char*)malloc(num);
}

void test(void)
{
	char* str = NULL;
	get_memory(&str, 100);
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	test();
	return 0;
}

上面的代码是可以打印出“hello world”的,但是遗憾的是上面的代码中使用了动态内存函数malloc,但是没有使用free函数释放动态内存空间
虽然上面的代码可以实现我们想要的效果,但这样的代码是存在安全隐患的

动态内存开辟函数malloccallocrealloc和动态内存释放函数free必须成对出现

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

void get_memory(char** p, size_t num)
{
	*p = (char*)malloc(num);
}

void test(void)
{
	char* str = NULL;
	get_memory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	test();
	return 0;
}

2.4 题目四

请问运行下面 text函数会有什么后果?

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

使用malloc函数申请一块100个字节大小的动态内存空间放入字符串“hello”,然后使用free函数释放这一动态内存空间
但是此时指针str中还存着我们开辟的动态内存空间的地址,正确的写法free函数后应紧跟str = NULL;,但是上面的代码并没有这一条语句
if语句判断的时候指针str确实是不为空指针的,进入if语句后执行strcpy(str, "world");这条代码,根据我们对strcpy函数的了解,这里还要对指针str解引用,但是指针str我们之前已经用free函数释放过了,并且没有赋NULL所以str此时是野指针不能解引用,运行起来程序就会出错
在这里插入图片描述

这道题考察的还是free函数后紧跟p = NULL的问题


3、柔性数组

3.1 什么是柔性数组

C99中,结构体中的最后一个成员允许是未知大小的数组,这就叫柔性数组成员

  • 在结构体中
  • 最后一个成员
  • 未知大小的数组
struct S1
{
	int n;
	char c;
	double d;
	int arr[];//未知大小的数组
};
struct S2
{
	int n;
	char c;
	double d;
	int arr[0];//未知大小的数组
};

上面两种写法中arr都是柔性数组成员
有些编译器可能只支持其中的一种写法,VS中两种写法都支持


3.2 柔性数组的特点

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

在这里插入图片描述

正是因为sizeof返回的这种结构大小不包含柔性数组的内存,所以结构中的柔性数组成员前面必须至少有一个其他成员,否则结构体的大小没法计算


3.3 柔性数组的使用

包含柔性数组的结构怎么使用呢?
包含柔性数组的结构创建变量不会像一般结构那样创建,而是使用malloc函数进行内存的动态分配

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

struct S
{
	int n;
	int arr[];
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));
	if (ps == NULL)
	{
		perror("malloc");
		//终止程序
		return;
	}
	//使用空间
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		ps->arr[i] = i + 1;
	}
	
	//...
	free(ps);
	ps = NULL;
	return 0;
}

在这里插入图片描述

柔性数组的柔性怎么体现呢?
因为上面包含柔性数组的结构是由malloc函数进行内存的动态分配,所以我们可以使用realloc函数进行动态内存的调整,那这个数组的大小就可大可小

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

struct S
{
	int n;
	int arr[];
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));
	if (ps == NULL)
	{
		perror("malloc");
		//终止程序
		return 1;
	}
	//使用空间
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		ps->arr[i] = i + 1;
	}
	//调整ps指向的空间大小
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int));
	//进行指针的非空判断,保护原地址
	if (ptr != NULL)
	{
		ps = ptr;
		//防止ptr变成野指针
		ptr = NULL;
	}
	else
	{
		perror("realloc");
		//终止程序
		return 1;
	}

	for (i = 0; i < 40; i++)
	{
		printf("%d ", ps->arr[i]);
	}

	//...
	free(ps);
	ps = NULL;
	return 0;
}

在这里插入图片描述

如果不使用柔性数组,还有一种办法能实现上面的效果

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

struct S
{
	int n;
	int* arr;
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int* tmp = (int*)malloc(20 * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc");
		return 1;
	}
	else
	{
		ps->arr = tmp;
		tmp = NULL;
	}
	ps->n = 100;
	int i = 0;
	//给指针arr指向的20个整型空间赋值
	for (i = 0; i < 20; i++)
	{
		ps->arr[i] = i + 1;
	}

	//调整指针arr指向的空间大小
	tmp = (int*)realloc(ps->arr, 40 * sizeof(int));
	if (tmp != NULL)
	{
		ps->arr = tmp;
		tmp = NULL;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	for (i = 0; i < 40; i++)
	{
		printf("%d ", ps->arr[i]);
	}

	//...
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
	return 0;
}

在这里插入图片描述

结构struct S中有一个指针成员,我们的想法是用malloc函数申请一块动态内存空间,再让结构中的这个指针指向这块动态分配的内存,然后这块由指针指向的动态内存空间就可以用realloc函数进行大小的调整了
可以看到这样实现的效果和柔性数组相似,那柔性数组为什么还要存在呢?
其实相比之下柔性数组还是有它的优势的


3.4 柔性数组的优势

  • 方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了两次内存分配,并把整个结构体返回给用户,用户调佣free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事
所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存释放

  • 这样有利于访问速度

连续的内存有利于提高访问速度,也有利于减少内存碎片
因为malloc等动态内存函数在申请空间时会在堆区允许的地方申请一块连续的空间,但是动态内存函数申请的多个动态内存空间之间并不是连续的,这些空间之间就形成了内存碎片


总结

  • 动态内存管理是一把双刃剑,它能给我们提供灵活的内存管理方式,但同样也会带来风险
  • 检查动态内存分配是否成功:在使用动态内存管理函数时,应该检查分配内存是否成功,以确保程序正常运行,这是比较容易忽略的点

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

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

相关文章

CTF Show MISC做题笔记

MISCX 30 题目压缩包为misc2.rar,其中包含三个文件:misc1.zip, flag.txt, hint.txt。其中后两个文件是加密的。 先解压出misc1.zip, 发现其中包含两个文件&#xff1a;misc.png和music.doc。其中后面文件是加密的。 解压出misc.png,发现图片尾部有消息&#xff1a;flag{flag…

竞拍商城系统源码后端PHP+前端UNIAPP

下载地址&#xff1a;竞拍商城系统源码后端PHP前端UNIAPP

使用CodeGen进行程序综合推理

Program Synthesis with CodeGen — ROCm Blogs (amd.com) CodeGen是基于标准Transformer的自回归语言模型家族&#xff0c;用于程序合成&#xff0c;正如作者所定义的&#xff0c;它是一种利用输入-输出示例或自然语言描述生成解决指定问题的计算机程序的方法。 我们将测试的…

哈希桶封装unordered_map、unordered_set

哈希桶源代码 我们将由下列的哈希桶来模拟封装STL库中的unordered_map和unordered_set 注意&#xff1a;为了实现封装unordered_map和unordered_set&#xff0c;我们需要对下列源码进行优化。 //哈希桶 namespace hashbucket {template<class K,class V>struct HashNo…

双列集合基础知识

package exercise;import java.util.HashMap; import java.util.Map;public class Demo1 {public static void main(String[] args) {Map<String, String> map new HashMap<>();//在添加数据的时候&#xff0c;如果键不存在&#xff0c;那么直接把键值对对象添加到…

Linux下线程的互斥与同步详解

&#x1f916;个人主页&#xff1a;晚风相伴-CSDN博客 &#x1f496;如果觉得内容对你有帮助的话&#xff0c;还请给博主一键三连&#xff08;点赞&#x1f49c;、收藏&#x1f9e1;、关注&#x1f49a;&#xff09;吧 &#x1f64f;如果内容有误或者有写的不好的地方的话&…

go语言实战--基于Vue3+gin框架的实战Cetide网项目(讲解开发过程中的各种踩坑)

最近被要求学习go语言开发&#xff0c;也就做一个项目实战巩固一下&#xff0c;也分享一下关于gin框架的实战项目 &#xff08;后续应该还是会继续学习Java&#xff0c;这一期还是做一个govue的&#xff09; 经过一段时间的开发过后&#xff0c;感觉现在的开发效率要快不少了&…

你可以直接和数据库对话了!DB-GPT 用LLM定义数据库下一代交互方式,数据库领域的GPT、开启数据3.0 时代

✨点击这里✨&#xff1a;&#x1f680;原文链接&#xff1a;&#xff08;更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号&#xff01;&#xff09; 你可以直接和数据库对话了&#xff01;DB-GPT 用LLM定义数据库下一代交互方式&#xff0c;数据库领…

如何理解与学习数学分析——第二部分——数学分析中的基本概念——第6章——级数

第2 部分&#xff1a;数学分析中的基本概念 (Concepts in Analysis) 6. 级数(Series) 本章从等比级数(geometric series)开始&#xff0c;研究可以使用公式计算无限和的条件。它讨论了部分和与级数收敛的符号、图形表示和定义&#xff0c;并将它们应用于调和级数。它介绍了级…

EKF在LiFePO4电池SOC估算中不好用?一问带你破解EKF应用难题

磷酸铁锂电池因为平台区的存在&#xff0c;导致使用戴维南模型EKF的方法时&#xff0c;无法准确进行SOC准确预估。所以最近搜索了大量关于磷酸铁锂电池SOC预估的论文、期刊&#xff0c;但我被海量忽略客观事实、仅为了毕业的硕士论文给震惊到了。很多论文为了掩饰平台区的存在&…

Live800:深度解析,客户服务如何塑造品牌形象

在当今竞争激烈的市场环境中&#xff0c;品牌形象对于企业的成功至关重要。而客户服务作为品牌与消费者之间最直接的互动方式&#xff0c;不仅影响着消费者的购买决策&#xff0c;更在塑造品牌形象方面发挥着不可替代的作用。本文将深度解析客户服务如何塑造品牌形象&#xff0…

python文件:py,ipynb, pyi, pyc, pyd, pyo都是什么文件?

1、Python文件类型介绍 &#x1f4c1; 1.1 .py 文件&#xff1a;源代码基础 .py 文件是 Python 最基本的源代码文件格式&#xff0c;用于存储纯文本形式的 Python 代码。它是开发者编写程序的主要场所&#xff0c;包含函数、类、变量定义以及执行逻辑。Python 解释器直接读取…

C++ OpenCV 图像分类魔法:探索神奇的模型与代码

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

React hooks动态配置侧边栏

React hooks根据不同需求 还有不同的角色 动态的去配置侧边栏 需求&#xff1a; 点击某个按钮是一套侧边栏 &#xff0c;不同角色&#xff08;比如管理员之类的权限高一点&#xff09;比普通用户多个侧边栏 然后点击另一个按钮是另一套侧边栏 此时&#xff0c;就需要动态的去…

解决微信小程序分享按钮不可用

问题描述 在微信小程序中点击胶囊按钮上的三个点&#xff0c;在弹出的对话框中的【分享给好友】【分享到朋友圈】按钮都属于不可用的状态&#xff0c;显示未设置。 问题截图 解决方案 在每个需要此功能的页面都需要添加此代码&#xff0c;否则就不能进行使用。 // vue3时&l…

基础乐理入门

基础概念 乐音&#xff1a;音高&#xff08;频率&#xff09;固定&#xff0c;振动规则的音。钢琴等乐器发出的是乐音&#xff0c;听起来悦耳、柔和。噪音&#xff1a;振动不规则&#xff0c;音高也不明显的音。风声、雨声、机器轰鸣声是噪音&#xff0c;大多数打击乐器&#…

在UI界面中实现3d人物展示

简要原理(设置双摄像机): 为需要展示的3D人物单独设置一个摄像机(只设置为渲染人物层级),主要摄像机的方向与人物方向一致,但摄像机需要需要旋转180,设置的角度自行进行微调创建一个Render Texture类型的组件用于存储摄像机渲染的内容UI上设置需要展示的图片区域,图片…

台湾合泰原装BS66F360 封装LQFP-44 电容触摸按键 AD+LED增强型触控

BS66F360是一款由Holtek Semiconductor Inc.生产的微控制器&#xff08;microcontroller&#xff09;&#xff0c;具有触摸检测和LED驱动功能。其应用领域广泛&#xff0c;包括但不限于以下几个方面&#xff1a; 1. 触摸按键应用&#xff1a;BS66F360内置了触摸按键检测功能&am…

【MySQL】聊聊MySQL常见的SQL语句阻塞场景

在平时的业务中&#xff0c;可能一个简单的SQL语句也执行很慢&#xff0c;这种情况其实大多数都是要么没有使用索引&#xff0c;要么出现锁竞争造成执行阻塞。本篇主要来介绍具体的场景 CREATE TABLE t ( id int(11) NOT NULL, c int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENG…

17.调用游戏本身的hp减伤害函数实现秒杀游戏角色

上一个内容&#xff1a;16.在目标进程构建CALL执行代码 16.在目标进程构建CALL执行代码在它的代码上进行的更改&#xff0c;它的callData变量中的代码不完善一个完整的函数是有return的&#xff0c;处理器执行到return会返回如果执行不到会继续往下走&#xff0c;直到执行不下…