如何方便的获取结构体成员的个数

如何方便的获取结构体成员的个数,第1张

如果数据时这样的话,考虑用struct2cell转为cell型的,这样比如 original_masks = daria_bend: [144x180x84 logical] daria_jack: [144x180x89 logical] 那转后就没有字段名称了

auto :声明自动变量 一般不使用 double :声明双精度变量或函数 int: 声明整型变量或函数 struct:声明结构体变量或函数 break:跳出当前循环 else :条件语句否定分支(与 if 连用) long :声明长整型变量或函数 switch :用于开关语句 case:开关语句分支 enum :声明枚举类型 register:声明积存器变量 typedef:用以给数据类型取别名(当然还有其他作用) char :声明字符型变量或函数 extern:声明变量是在其他文件正声明(也可以看做是引用变量) return :子程序返回语句(可以带参数,也看不带参数) union:声明联合数据类型 const :声明只读变量 float:声明浮点型变量或函数 short :声明短整型变量或函数 unsigned:声明无符号类型变量或函数 continue:结束当前循环,开始下一轮循环 for:一种循环语句(可意会不可言传) signed:生命有符号类型变量或函数 void :声明函数无返回值或无参数,声明无类型指针(基本上就这三个作用) default:开关语句中的“其他”分支 goto:无条件跳转语句 sizeof:计算数据类型长度 volatile:说明变量在程序执行中可被隐含地改变 do :循环语句的循环体 while :循环语句的循环条件 static :声明静态变量 if:条件语句 1)auto 这个这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为所有的变量默认就是auto的。 (2)register 这个关键字命令编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。 (3)static 常见的两种用途: 1>统计函数被调用的次数; 2>减少局部数组建立和赋值的开销变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组声明为static类型,以减少建立或者初始化这些变量的开销 详细说明: 1>、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。 2>、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。 3>当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局静态储存区。 使用注意: 1>若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度; 2>若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度; 3>设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(只要输入数据相同就应产生相同的输出) (4)const 被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。 作用: 1>修饰输入参数 a对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。 b对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。 2>用const修饰函数的返回值 a如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。 如对于: const char GetString(void); 如下语句将出现编译

这要从数据区和代码区说起。类的定义一定是放在代码区,是生成对应成员变量和成员函数代码的集合。而开创类的实例(对象)时,对象中的成员变量一定存在于数据区,一般应该占用了连续的存储空间,而对象中的成员函数一定是以指针形式指向代码区的函数,然后通过获取参数,完成相应功能。

所以总的来说,汇编中实现结构体或类,代码一定繁复异常。你可以通过编译器DEBUG,看看汇编如何实现。

要实现程序运行时得到结构体的变量名字,可以参考MFC中的实现方法。用宏函数来处理了。。如下测试代码:

#include <<a href=">

直接通过取地址符 & 进行获取。

例如 :

struct Student

{

            char Name[10];

            int Age;

            char Sex;

}

Student  stu;

&stuAge  // 获取 Age 的地址

在 C 语言中,可以使用结构体( Struct )来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员( Member )。结构体的定义形式为:

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。结构体变量才包含了实实在在的数据,需要内存空间来存储。

stu 为结构体名,里面包含name、num、age、group、score这5个成员。 stu1 和 stu2 则为两个stu类型的结构体变量。

直接将变量放在结构体的最后即可。

如上所示,在 stu 结构体里还定义了『结构体变量sub1』和『结构体sub2』,由于 sub2 没有定义变量,所以其内部成员 score 即为母结构体stu的成员变量。

使用点号 获取结构体变量的单个成员,然后再进行赋值 *** 作。

也可以在定义结构体变量时整体赋值:

结构体中各成员在内存中是按顺序依次存储的,成员之间不互相影响,各占用不同的内存空间。结构体变量占用的内存大于等于所有成员占用的内存的总和,因为成员在存储时需要遵循结构体的内存对齐规则,成员之间可能会存在裂缝。

先来看看结构体的内存对齐规则:

看完内存对齐规则是不是感觉有点绕?不急,接下来通过分析具体例子来理解这个规则。

示例1:含有多种数据类型成员

输出结果分析:

根据上面的分析可知,struct1的成员总共需要18字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为24。

示例2:交换成员位置

这次在示例1中struct的基础上交换了成员b和c的位置,输出结果就不一样了,分析如下:

根据上面的分析可知,struct2的成员总共需要16字节内存,根据规则3,struct2的内存大小必须是8(double a)的整数倍,所以最后内存大小为16。

示例3:结构体嵌套结构体

直接在struct1里加上一个struct2成员,然后输出内存大小

从之前struct1的分析可知,a、b、c、d实际占用18字节(位置0-17),那成员e就需要从位置18开始存放。由于e是个结构体,根据规则2, 当结构体作为成员时,需要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储 。结构体e中内存占用最大的元素是 double a ,为8字节,所以e就需要从8的整数倍地址开始存储,即后移到位置24开始存储,e本身占用16字节内存,所以存放位置是24-39。

根据上面的分析可知,struct1的成员总共需要40字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为40。

1 数据结构 -- 共用体Union

2 数据结构 -- 位域

如下结构体

在 汇编模式 下,可知 结构体 的 函数 调用方式是 静态调用 (直接调用):

通过在 MachOView 中打开 可执行文件 :

通过上图可知:在 调用函数 时,不用再去其他地方查找teach的函数地址, 编译链接 完成之后,地址就已经 确定 放在 text 字段里;所以说 结构体 的函数调度方式是 静态 调度,意味着 结构体 是 不 会 存储 其中的 函数 ,执行 效率 非常 高 。

存储的是 符号 位于( String Table )字符串表中的 位置 , 不 直接存储 符号 。

符号 经过 swift 命令重整( nm )变成了符号表中存放的内容。

所以也可以通过以下命令在 终端 拿到 符号表 :

其中:

path --> 可执行文件 的地址

addr -->指定 函数地址

如下图:

在 Release 模式下,会多生成一个 dsYM 文件用于 捕获崩溃 、 查找debug 信息,在 线上使用 该文件。 符号表 中 不 再 保留 那些静态链接的函数符号(在字符串表中的位置信息),因为一旦编译完成就能确定地址,这时符号表 精简很多 ,不占用macho文件大小,保留的是那些 不能确定地址的符号 (在字符串表中的位置)。

总结:静态调度的函数一旦 编译完成 就能确定 地址 ,再通过 地址 调用函数,只是在 debug 模式下为了 方便调试 才将该地址的 符号信息 以字符串形式 存储 在 字符串表 中,在字符串表中的 位置信息 又 存储 在 符号表 中,并 不 是通过 符号表 中去查找到函数 地址 再进行调度,要注意 先后顺序 。

首先需了解:

程序的 静态基地址 :在 Load Commands 中 __TEXT 字段里, VM Address 就是静态基地址。

程序运行 首地址 :在 lldb 中通过 image list 命令来 查看 首地址。

随机偏移地址 :在可执行程序随机装载到内存中时的随机地址,就是我们当前这application偏移的地址。可通过 程序运行首地址 - 程序的静态基地址 得到。

最终: 静态函数的地址 = 符号表中函数地址 + 随机偏移地址

通过上图可知:

偏移地址 = 程序运行首地址 - 程序的静态基地址即 0x5a47000

计算一下:静态函数的地址 = 符号表中函数地址 + 随机偏移地址 即

0x105a48db0 = 0x100001DB0 + 0x5a47000

这张表的本质其实就类似我们理解的 数组 ,声明在 class 内部的方法在 不 加任何 关键字 修饰的过程中, 连续 存放在我们当前的 地址空间 中。

首先了解 ARM64 下的几个 汇编指令 :

通过以下例子在汇编模式下:

可以看出上面的函数都是按 顺序 放在 函数表 中。

接下来通过 SIL 中查源码断点来看一下:

可看出 V-Table 就是一个 数组 结构。

如果更改方法声明的 位置 ,将方法放在 extension 中声明:

汇编模式下可看出:如果方法声明放在 extension 中,则是直接 地址调用 。为什么呢?举个例子:在Swift中,一个类有 子类 ,有 extension ,extension可以写在任意Swift文件中,如果 子类 所在文件优 先 于 extension 所在文件 加载 ,子类的函数表会首先 继承 父类的函数表,其次是自己的函数列表,当加载到extension时发现有函数,这时子类中没有指针记录哪些是父类方法哪些是自己的方法,就没法将extension中的方法按 顺序 的插入自己的 函数表 中。

扩展:OC中分类方法的调用

汇编模式下直接 地址调用 :

SIL 的 V-Table 中也没有加入 final 修饰的 teach 函数:

OC-Swift 桥接演示:

在 OC 项目中新建 Swift 文件并选择 Create Bridging Header ,Swift中:

要在 OC 中使用 Swift 文件,就需要导入 头文件 ,头文件查看方式如下:

如果 YYTeacher 不继承 NSObject ,该头文件中则没有与 YYTeacher 相关的类信息,就不能访问到 YYTeacher 这个类。

继承 NSObject 后头文件中才有下列信息:

接下来在 OC 文件中:

在 OC 中,只能访问到有 @objc 修饰的 teach 函数,而没有 @objc 修饰的 teach1 则不能被访问到。

这时调用 tteach() 打印的则是 teach1 , @_dynamicReplacement(for:teach) 在 extension 中将 teach() 动态替换成 teach1() 。

内存分区 模型如下图:

上面例子中的 age 就存放在 栈 内存中。

上面例子中的 t里面存放的地址 就是在 堆区 地址。

在上面例子中,

注意: SEGMENT 和 SECTION 是 Macho 文件对 格式 的划分,而内存分区是人为对 内存布局 的分区,所以对于上面例子中 a 存放在 全局区 和在 Macho 文件中存放 __DATA__data 里面互不冲突。

从上面中可以看出, 全局已初始化变量 a和age2的地址比较 接近 ,而且比 全局未初始化变量 的地址 低 ,可以更详细的对全局区进行分区:

如果例子中加入 全局已初始化静态常量

因为 age3 是 静态不可修改 的,macho文件直接 不 会记录 age3 的 符号 信息, 赋值 过程中对于编译器来说 age3 这个 符号 根本 不存在 ,就是一个值 30 ,这里的 int b = age3 就相当于 int b = 30 。

对于Swift来说, let age = 10

这种情况下,因为age是不可变的,所以不允许通过 po withUnsafePointer(to: &age){print($0)} 这种方式来获取age的地址。

可以通过以下方式在 汇编模式 下来获取 age 的地址为 0x100008028 :

可知 age 的符号信息在 macho 文件中存放在 __DATA__common 里面

综上可知:和 C/OC 相比, Swift 对于 全局 变量在 Macho 文件中的 划分规则 是 不一样 的

以上就是关于如何方便的获取结构体成员的个数全部的内容,包括:如何方便的获取结构体成员的个数、汇编中用什么命令代码来获取函数的返回值、汇编语言如何实现类等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9783105.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-01
下一篇2023-05-01

发表评论

登录后才能评论

评论列表(0条)

    保存