C语言深度剖析书籍学习记录 第四章 指针和数组

  •  p 称为指针变量,p 里存储的内存地址处的内存称为 p 所指向的内存。 指针变量 p 里存储的任何数据都将被当作地址来处理
  • 一个基本的数据类型(包括结构体等自定义类型)加上“*” 号就构成了一个指针类型的模子。这个模子的大小是一定的,与“*”号前面的数据类型无 关。
  • “*”号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以,在 32 位 系统下,不管什么样的指针类型,其大小都为 4byte。可以测试一下 sizeof(void *)。
  • int *p = NULL;  这时候通过编译器查看 p 的值为 0x00000000。这句代码的意思是:定义一个指针 变量 p,其指向的内存里面保存的是 int 类型的数据;
  • 在定义变量 p 的同时把 p 的值设置为 0x00000000,而不是把*p 的值设置为 0x00000000。
  • 这个过程叫做初始化,是在编译的时候 进行的。避免野指针,指向一段关键数据的地址造成数据的损毁
  • int *p;  *p = NULL; 同样,我们可以在编译器上调试这两行代码。第一行代码,定义了一个指针变量 p,其指向 的内存里面保存的是 int 类型的数据;但是这时候变量 p 本身的值是多少不得而知,也就是 说现在变量 p 保存的有可能是一个非法的地址。第二行代码,给*p 赋值为 NULL,即给 p 指向的内存赋值为 NULL;但是由于 p 指向的内存可能是非法的,所以调试的时候编译器可 能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使 p 指向一块合 法的内存:
  • int i = 10; int *p = &i; *p = NULL;  在编译器上调试一下,我们发现 p 指向的内存由原来的 10 变为 0 了;而 p 本身的值, 即内存地址并没有改变。经过上面的分析,相信你已经明白它们之间的区别了。不过这里还有一个问题需要注 意,也就是这个 NULL。初学者往往在这里犯错误。注意 NULL 就是 NULL,它被宏定义为 0:
    #define NULL 0
  • 很多系统下除了有 NULL 外,还有 NUL(Visual C++ 6.0 上提示说不认识 NUL)。NUL 是 ASCII 码表的第一个字符,表示的是空字符,其 ASCII 码值为 0。其值虽然都为 0,但表示的意思 完全不一样。同样,NULL 和 0 表示的意思也完全不一样。一定不要混淆。
  • 另外还有初学者在使用 NULL 的时候误写成 null 或 Null 等。这些都是不正确的,C 语 言对大小写十分敏感啊。当然,也确实有系统也定义了 null,其意思也与 NULL 没有区别, 但是你千万不用使用 null,这会影响你代码的移植性。

将数值存储到指定的内存地址

  • 假设现在需要往内存 0x12ff7c 地址上存入一个整型数 0x100。我们怎么才能做到呢?我 们知道可以通过一个指针向其指向的内存地址写入数据,那么这里的内存地址 0x12ff7c 其 本质不就是一个指针嘛。所以我们可以用下面的方法:   int *p = (int *)0x12ff7c;   *p = 0x100;
  • 需要注意的是将地址 0x12ff7c 赋值给指针变量 p 的时候必须强制转换。至于这里为什么选择内存地址 0x12ff7c,而不选择别的地址,比如 0xff00 等。这仅仅是为了方便在 Visual C++ 6.0 上测试而已。如果你选择 0xff00,也许在执行*p = 0x100;这条语句的时候,编译器 会报告一个内存访问的错误,因为地址 0xff00 处的内存你可能并没有权力去访问。
  • 既然这 样,我们怎么知道一个内存地址是可以合法的被访问呢?也就是说你怎么知道地址 0x12ff7c 处的内存是可以被访问的呢?其实这很简单,我们可以先定义一个变量 i,比如:int i = 0;  变量 i 所处的内存肯定是可以被访问的。然后在编译器的 watch 窗口上观察&i 的值不就 知道其内存地址了么?这里我得到的地址是 0x12ff7c,仅此而已(不同的编译器可能每次给 变量 i 分配的内存地址不一样,而刚好 Visual C++ 6.0 每次都一样)。你完全可以给任意一个 可以被合法访问的地址赋值。得到这个地址后再把“int i = 0;”这句代码删除。
  • *(int *)0x12ff7c = 0x100;  这行代码其实和上面的两行代码没有本质的区别。先将地址 0x12ff7c 强制转换,告诉编译 器这个地址上将存储一个 int 类型的数据;然后通过钥匙“*”向这块内存写入一个数据。

  • 如上图所示,当我们定义一个数组 a 时,编译器根据指定的元素个数和元素的类型分配确定 大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为 a。名字 a 一旦 与这块内存匹配就不能被改变。a[0],a[1]等为 a 的元素,但并非元素的名字。数组的每一个 元素都是没有名字的。那现在再来回答第一章讲解 sizeof 关键字时的几个问题:
  • sizeof(a)的值为 sizeof(int)*5,32 位系统下为 20。
  • sizeof(a[0])的值为 sizeof(int),32 位系统下为 4。
  • sizeof(a[5])的值在 32 位系统下为 4。并没有出错,为什么呢?我们讲过 sizeof 是关键字 不是函数。函数求值是在运行的时候,而关键字 sizeof 求值是在编译的时候。虽然并不存在 a[5]这个元素,但是这里也并没有去真正访问 a[5],而是仅仅根据数组元素的类型来确定其 值。所以这里使用 a[5]并不会出错。
  • sizeof(&a[0])的值在 32 位系下为 4,这很好理解。取元素 a[0]的首地址。 sizeof(&a)的值在 32 位系统下也为 4,这也很好理解。取数组 a 的首地址
  • &a[0]和&a 的区别   a[0]是一个元素,a 是整个数组,虽然&a[0]和&a 的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。举个 例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的 意义完全不同。这里也是同一个意思。 

数组名 a 作为左值和右值的区别

  • 简单而言,出现在赋值符“=”右边的就是右值,出现在赋值符“=”左边的就是左值。  比如,x=y。
  • 左值:在这个上下文环境中,编译器认为 x 的含义是 x 所代表的地址。这个地址只有 编译器知道,在编译的时候确定,编译器在一个特定的区域保存这个地址,我们完全不必考虑这个地址保存在哪里。
  • 右值:在这个上下文环境中,编译器认为 y 的含义是 y 所代表的地址里面的内容。这 个内容是什么,只有到运行时才知道。
  • C 语言引入一个术语-----“可修改的左值”。意思就是,出现在赋值符左边的符号所代 表的地址上的内容一定是可以被修改的。换句话说,就是我们只能给非只读变量赋值。
  • 当 a 作为右值的时候代表的是什么意思呢?很多书认为是数组的首地址,其实这是非常 错误的。a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组 的首地址。这是两码事。但是注意,这仅仅是代表,并没有一个地方(这只是简单的这么 认为,其具体实现细节不作过多讨论)来存储这个地址,也就是说编译器并没有为数组 a 分配一块内存来存其地址,这一点就与指针有很大的差别。
  • a 不能作为左值!编译器会认为数组名作为左值代表 的意思是 a 的首元素的首地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。所以我们可以把 a[i]当左值,而无法把 a 当左值。其实我们完全可以把 a 当一个普通的变量来看,只不过这个变量内部分为很多小块, 我们只能通过分别访问这些小块来达到访问整个变量 a 的目的。
  • A),char *p = “abcdef”;
  • B),char a[] = “123456”;

以指针的形式访问和以下标的形式访问指针

  • 例子 A)定义了一个指针变量 p,p 本身在栈上占 4 个 byte,p 里存储的是一块内存的首地址。这块内存在静态区,其空间大小为 7 个 byte,这块内存也没有名字。对这块内存的访 问完全是匿名的访问。比如现在需要读取字符‘e’,我们有两种方式:
  • 1),以指针的形式:*(p+4)。先取出 p 里存储的地址值,假设为 0x0000FF00,然后加 上 4 个字符的偏移量,得到新的地址 0x0000FF04。然后取出 0x0000FF04 地址上的值。
  • 2),以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出 p 里存储的地址值,然后加上中括号中 4 个元素的偏 移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上 与以指针的形式访问没有区别,只是写法上不同罢了。
  • 偏移量的单位是元 素的个数而不是 byte 数

#include <iostream>int main(){int a[] = {1,2,3,4,5,6,7,8,9,10};int *ptr = (int*)(&a + 1);printf("%d,%d",*(a+1),*(ptr-1));
}
  • 对指针进行加 1 操作,得到的是下一个元素的地址,而不是原有地址值直接加 1。所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 因此,对上题来说,a 是一个一 维数组,数组中有 5 个元素; ptr 是一个 int 型的指针。 

  • 这就是为什么 extern char a[]与 extern char a[100]等价的原因。因为这只是声明,不分配 空间,所以编译器无需知道这个数组有多少个元素。这两个声明都告诉编译器 a 是在别的文 件中被定义的一个数组,a 同时代表着数组 a 的首元素的首地址,也就是这块内存的起始地 址。数组内地任何元素的的地址都只需要知道这个地址就可以计算出来。
  • 但是,当你声明为 extern char *a 时,编译器理所当然的认为 a 是一个指针变量,在 32 位系 统下,占 4 个 byte。这 4 个 byte 里保存了一个地址,这个地址上存的是字符类型数据。虽 然在文件 1 中,编译器知道 a 是一个数组,但是在文件 2 中,编译器并不知道这点。大多数 编译器是按文件分别编译的,编译器只按照本文件中声明的类型来处理。所以,虽然 a 实际 大小为 100 个 byte,但是在文件 2 中,编译器认为 a 只占 4 个 byte。
  • 编译器会把存在指针变量中的任何数据当作地址来处理。所以,如果需要 访问这些字符类型数据,我们必须先从指针变量 a 中取出其保存的地址。如下图: 

 

  • 在文件 1 中,编译器分配 4 个 byte 空间,并命名为 p。同时 p 里保存了字符串常量“abcdefg” 的首字符的首地址。这个字符串常量本身保存在内存的静态区,其内容不可更改。
  • 在文件 2 中,编译器认为 p 是一个数组,其大小为 4 个 byte,数组内保存的是 char 类型的数据。
  • 在 文件 2 中使用 p 的过程如下图: 

 

 指针数组和数组指针

  • 初学者总是分不出指针数组与数组指针的区别。其实很好理解:
  • 指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。
  • 数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节, 至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
  • 下面到底哪个是数组指针,哪个是指针数组呢:
    • A),int *p1[10];         指针数组
    • B),int (*p2)[10];       数组指针
  • 这里需要明白一个符号之间的优先级问题。 “[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为 p1,int * 修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含 10 个 指向 int 类型数据的指针,即指针数组。
  • 至于 p2 就更好理解了,在这里“()”的优先级比 “[]”高,“*”号和 p2 构成一个指针的定义,指针变量名为 p2,int 修饰的是数组的内容, 即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚 p2 是一个指 针,它指向一个包含 10 个 int 类型数据的数组,即数组指针。
  • 我们可以借助下面的图加深 理解:

  • char a[5]={'A','B','C','D'};
  • char (*p3)[5] = &a;     正确
  • char (*p4)[5] = a;       需要类型转换
  • &a 是整个数组的首地址,a 是数组首元素的首地址,其值相同但意义不同

 

  •  根据上面的讲解,&a+1 与 a+1 的区别已经清楚。
  • ptr1:将&a+1 的值强制转换成 int*类型,赋值给 int* 类型的变量 ptr,ptr1 肯定指到数 组 a 的下一个 int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即 ptr1 往后退 4 个 byte。所以其 值为 0x4。
  • ptr2:按照上面的讲解,(int)a+1 的值是元素 a[0]的第二个字节的地址。然后把这个地址 强制转换成 int*类型的值赋给 ptr2,也就是说*ptr2 的值应该为元素 a[0]的第二个字节开始的 连续4个byte的内容。
  • 其内存布局如下图:

 

 

  •  使用一个指针分配内存空间,复制数据
  • 在运行 strcpy(str,”hello”)语句的时候发生错误。这时候观察 str 的值,发现仍然为 NULL。 也就是说 str 本身并没有改变,我们 malloc 的内存的地址并没有赋给 str,而是赋给了_str。 而这个_str 是编译器自动分配和回收的,我们根本就无法使用。所以想这样获取一块内存是 不行的。那怎么办?
  • 两个办法: 使用return返回内存空间 
  • 使用二级指针

 

  • 注意,这里的参数是&str 而非 str。这样的话传递过去的是 str 的地址,是一个值。在函 数内部,用钥匙(“*”)来开锁:*(&str),其值就是 str。所以 malloc 分配的内存地址是真正 赋值给了 str 本身。 

函数指针

  • char * (*fun1)(char * p1,char * p2);   fun1 指针变量指向的是一个函数

 

  •  给函数指针赋值时,可以用&fun 或直接用函数名 fun。这是因为函数名被 编译之后其实就是一个地址,所以这里两种用法没有本质的差别

*(int*)&p ----这是什么?

  • void (*p)();    这行代码定义了一个指针变量 p,p 指向一个函数,这个函数的参数和返回值都是 void。
  • &p 是求指针变量 p 本身的地址,这是一个 32 位的二进制常数(32 位系统)。
  • (int*)&p 表示将地址强制转换成指向 int 类型数据的指针。
  • (int)Function 表示将函数的入口地址强制转换成 int 类型的数据。
  • *(int*)&p=(int)Function;表示将函数的入口地址赋值给指针变量 p。
  • 那么(*p) ();就是表示对函数的调用。
  • 其实函数指针与普通指针没什么差别,只是指向的内容不同而已。 使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更
    容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦 合度以及使接口与实现分开。

(*(void(*) ())0)();

  • 第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
  • 第二步:(void(*) ())0,这是将 0 强制转换为函数指针类型,0 是一个地址,也就是说一个函数存在首地址为 0 的一段区域内。
  • 第三步:(*(void(*) ())0),这是取 0 地址开始的一段内存里面的内容,其内容就是保存在首地址为 0 的一段区域内的函数。
  • 第四步:(*(void(*) ())0)(),这是函数调用。

函数指针数组  char * (*pf[3])(char * p);

  • 这是定义一个函数指针数组。它是一个数组,数组名为 pf,数组内存储了 3 个指向函数的 指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函 数。这念起来似乎有点拗口。不过不要紧,关键是你明白这是一个指针数组,是数组

 

函数指针数组指针 char * (*(*pf)[3])(char * p);

  • 注意,这里的pf和上一节的pf就完全是两码事了。上一节的pf并非指针,而是一个数组名; 这里的 pf 确实是实实在在的指针。这个指针指向一个包含了 3 个元素的数组;这个数字里 面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个 指向字符的指针的函数。

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js中select下拉框重置_如何利用CSS3制作炫酷的下拉框

很多小伙伴都不清楚CSS3是做什么&#xff1f;用途是什么&#xff1f;接下来我就给展示一个css3制作一个炫酷下拉框。其实不只是这些&#xff0c;还有很多。CSS3是CSS(层叠样式表)技术的升级版本&#xff0c;于1999年开始制订&#xff0c;2001年5月23日W3C完成了CSS3的工作草案&…

select选择框必输校验_轮子这么多,我们为什么选择自研NewSQL

作者介绍李鑫&#xff0c;滴滴资深软件开发工程师&#xff0c;多年分布式存储领域设计及开发经验。曾参与NoSQL/NewSQL数据库Fusion、分布式时序数据库sentry、NewSQL数据库SDB等系统的设计开发工作。一、背景Fusion-NewSQL是由滴滴自研的在分布式KV存储基础上构建的NewSQL存储…

C语言深度剖析书籍学习记录 第五章 内存管理

常见的内存错误 定义了指针变量&#xff0c;但是没有为指针分配内存&#xff0c;即指针没有指向一块合法的内存。 结构体成员指针未初始化 很多初学者犯了这个错误还不知道是怎么回事。这里定义了结构体变量 stu&#xff0c;但是他没 想到这个结构体内部 char *name 这成员在定…

怎么改电脑网络ip地址_抛弃重启路由器获取ip地址方式,巧妙运用ip代理改IP工具...

网络是简单的也是复杂的&#xff0c;在如此庞大的网络世界里有太多的不确定因素&#xff0c;导致我们遇到IP限制问题&#xff0c;从而影响到我们的网络访问&#xff0c;而大家都知道&#xff0c;如果遇到ip被限制的问题&#xff0c;最快速直接的办法就是把被限制的ip更换一个新…

C语言深度剖析书籍学习记录 第六章 函数

函数的好处 1、降低复杂性:使用函数的最首要原因是为了降低程序的复杂性&#xff0c;可以使用函数来隐含信息&#xff0c;从而使你不必再考虑这些信息。2、避免重复代码段:如果在两个不同函数中的代码很相似&#xff0c;这往往意味着分解工作有误。这时&#xff0c;应该把两个…

如何把word分装到两个byte_如何核对两个Word文档的内容差别?同事加班半小时,我只花了30秒...

昨天下班前&#xff0c;老板突然发了两份Word文档过来&#xff0c;一份是原稿&#xff0c;还有一份是修订稿&#xff0c;叫我们找出两份文档的内容差别之处&#xff0c;我只花了30秒就搞定了&#xff0c;然后准时下班&#xff01;你想知道我是怎么操作的吗&#xff1f;下面小源…

stm32f767中文手册_ALIENTEK 阿波罗 STM32F767 开发板资料连载第五章 SYSTEM 文件夹

1)实验平台&#xff1a;alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号&#xff0c;获取更多资料&#xff1a;正点原子第五章 SYSTEM 文件夹介绍第三章&#xff0c;我们介绍了如何在 MDK5 下建立 STM32F7 工程。在这个新建的工程之…

手机安卓学习 内核开发

官网开源代码 Documentation - MiCode/Xiaomi_Kernel_OpenSource - Sourcegraph Xiaomi 11T Pro GitHub - MiCode/Xiaomi_Kernel_OpenSource: Xiaomi Mobile Phone Kernel OpenSourceAndroid 开源项目 | Android Open Source Project google安卓官网 目录概览 参考…

vs 启动调用的目标发生异常_如何解决不可测、异常场景的问题?

阿里QA导读&#xff1a;在软件研发过程中&#xff0c;发布前跨多个系统的联调测试是不可或缺的一环&#xff0c;而在联调过程中&#xff0c;经常会遇到一些比较棘手的困难&#xff0c;阻塞整个联调进程。其中比较典型的有&#xff1a;第三方的研发节奏不一致&#xff0c;导致无…

Linux内核 scatterlist介绍

scatterlist 物理内存的散列表。通俗讲&#xff0c;就是把一些分散的物理内存&#xff0c;以列表的形式组织起来 诞生背景 假设有三个模块可以访问memory&#xff1a;CPU、DMA控制器和某个外设。CPU通过MMU以虚拟地址&#xff08;VA&#xff09;的形式访问memory&#xff1b;…

Linux内核 crypto文件夹 密码学知识学习

密码算法分类 对称算法非对称算法消息摘要&#xff08;单向哈希&#xff09;算法这些算法作为加密函数框架的最底层&#xff0c;提供加密和解密的实际操作。这些函数可以在内核crypto文件夹下&#xff0c;相应的文件中找到。不过内核模块不能直接调用这些函数&#xff0c;因为…

Linux crypto相关知识的汇总 Linux加密框架crypto中的算法和算法模式(一)

Linux加密框架中的算法和算法模式 Linux加密框架中的算法和算法模式&#xff08;一&#xff09;_家有一希的博客-CSDN博客 加密框架支持的密码算法主要是对称密码算法和哈希算法&#xff0c;暂时不支持非对称密码算法。除密码算法外&#xff0c;加密框架还包括伪随机数生成算法…

Linux crypto相关知识的汇总 Linux加密框架crypto对称算法和哈希算法加密模式

参考链接 Linux加密框架中的算法和算法模式&#xff08;二&#xff09;_家有一希的博客-CSDN博客 对称算法 分组算法模式 ECB模式 ECB模式下&#xff0c;明文数据被分为大小合适的分组&#xff0c;然后对每个分组独立进行加密或解密如下图所示如果两个明文块相同&#xff0c…

Linux加密框架中的算法和算法模式

参考链接 Linux加密框架中的算法和算法模式&#xff08;三&#xff09;_家有一希的博客-CSDN博客 对称算法 14 如上所示&#xff0c;在arc4.c中定义了两个与RC4算法相关的算法实现&#xff0c;分别为arc4和ecb(arc4)&#xff0c;其中arc4是RC算法的算法实现&#xff0c;而ecb…

Linux加密框架crypto AES代码相关

例子 aes_generic.c - crypto/aes_generic.c - Linux source code (v5.15.11) - Bootlin static struct crypto_alg aes_alg {.cra_name "aes",.cra_driver_name "aes-generic",.cra_priority 100,.cra_flags CRYPTO_ALG_TYPE_CIPHER,.cra_blocks…

Linux加密框架 crypto RC4

参考链接 arc4.h Linux加密框架中的主要数据结构&#xff08;一&#xff09;_家有一希的博客-CSDN博客 头文件 arc4.h - include/crypto/arc4.h - Linux source code (v5.15.11) - Bootlin实现代码 arc4.c arc4.c - crypto/arc4.c - Linux source code (v5.15.11) - Bootlin…

Linux加密框架 crypto 哈希算法说明 同步哈希shash_alg | 异步哈希 ahash_alg | 通用部分抽象 hash_alg_common

参考链接 Linux加密框架中的主要数据结构&#xff08;二&#xff09;_家有一希的博客-CSDN博客 定义 通用算法说明数据结构crypto_alg的联合体成员变量cra_u中包含多种算法的个性化属性&#xff0c;如分组算法、块加密算法、压缩算法、伪随机数算法等&#xff0c;但不包含哈希…

Linux加密框架 crypto 哈希算法举例 MD5

参考链接 Linux加密框架 crypto 哈希算法说明 同步哈希shash_alg | 异步哈希 ahash_alg | 通用部分抽象 hash_alg_common_CHYabc123456hh的博客-CSDN博客Linux加密框架中的主要数据结构&#xff08;二&#xff09;_家有一希的博客-CSDN博客 MD5 md5.h - include/crypto/md5.h …

事务没提交的数据查的出来吗?_“金三银四”面试官:说说事务的ACID,什么是脏读、幻读?...

一、事务事务是数据库管理系统执行过程中的一个逻辑单位&#xff0c;由一个有限的数据库操作序列构成。--摘自百科在MySQL里&#xff0c;事务是在引擎层面实现&#xff0c;比如MyIsam不支持&#xff0c;InnoDB支持面试清单&#xff08;Java岗&#xff09;&#xff1a;JavaJVM数…