iOS ------ tagged Pointer 内存对齐

一,tagged Pointer

为了节省内存和提高执行效率,苹果在64bit程序中引入了Tagged Pointer计数,用于优化NSNumber, NSDate, NSString等小对象的存储。一个指针或地址区域,除了放对象地址之外,也可以放其他额外的信息,并将其中的一些bit位作为tag标记区分,这就叫做Tagged Pointer

从占用内存来看

指针类型的大小通常也是与 CPU 位数相关,一个指针所在 32 bit 下占用 4 个字节,在 64 bit 下占用 8 个字节。
NSNumber等对象的指针中存储的数据变成了Tag+Data形式(Tag为特殊标记,用于区分NSNumber、NSDate、NSString等对象类型;Data为对象的值)。这样使用一个NSNumber对象只需要 8 个字节指针内存。当指针的 8 个字节不够存储数据时,才会在将对象存储在堆上。

在 64 bit 下,如果没有使用Tagged Pointer的话,为了使用一个NSNumber对象就需要 8 个字节指针内存和 32 个字节对象内存。

    NSInteger i = 0xFFFFFFFFFFFFFF;NSNumber *number = [NSNumber numberWithInteger:i];NSLog(@"%zd", malloc_size((__bridge const void *)(number))); // 32NSLog(@"%zd", sizeof(number)); // 8

使用了Tagged Pointer且指针的8歌字节够存储数据,NSNumber对象的值直接存储在了指针上,不会在堆上申请内存。则使用一个NSNumber对象只需要指针的 8 个字节内存就够了,大大的节省了内存占用。

NSInteger i = 1;NSNumber *number = [NSNumber numberWithInteger:i];NSLog(@"%zd", malloc_size((__bridge const void *)(number))); // 0NSLog(@"%zd", sizeof(number)); // 8

从效率上来看

为了使用一个NSNumber对象,需要在堆上为其分配内存,还要维护它的引用计数,管理它的生命周期,影响执行的效率

NSNumber

int main(int argc, const char * argv[]) {@autoreleasepool {NSNumber *number1 = @1;NSNumber *number2 = @2;NSNumber *number3 = @3;NSNumber *number4 = @(0xFFFFFFFFFFFFFFFF);NSLog(@"%p %p %p %p", number1, number2, number3, number4);}return 0;
}
// 关闭 Tagged Pointer 数据混淆后:0x127 0x227 0x327 0x600003a090e0
// 关闭 Tagged Pointer 数据混淆前:0xaca2838a63a4fb34 0xaca2838a63a4fb04 0xaca2838a63a4fb14 0x600003a090e0

number1~number3指针为Tagged Pointer类型,可以看到对象的值都存储在了指针中,对应0x1、0x2、0x3。而number4由于数据过大,指针的8个字节不够存储,所以在堆中分配了内存。

0x127 中的 2 和 7 表示什么?
我们先来看这个7,0x127为十六进制表示,7的二进制为0111。最后一位1是Tagged Pointer标识位,代表这个指针是Tagged Pointer。前面的011是类标识位,对应十进制为3,表示NSNumber类。

可以在Runtime源码objc4中查看NSNumber、NSDate、NSString等类的标识位

// objc-internal.h
{OBJC_TAG_NSAtom            = 0, OBJC_TAG_1                 = 1, OBJC_TAG_NSString          = 2, OBJC_TAG_NSNumber          = 3, OBJC_TAG_NSIndexPath       = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate            = 6,......
}

0x127 中的 2(即倒数第二位)又代表什么呢?

倒数第二位用来表示数据类型。

Tagged Pointer倒数第二位对应数据类型:

0: char
1: short
2: int
3: long
4: float
5: double

在这里插入图片描述

NSString

int main(int argc, const char * argv[]) {@autoreleasepool {NSString *a = @"a";NSMutableString *b = [a mutableCopy];NSString *c = [a copy];NSString *d = [[a mutableCopy] copy];NSString *e = [NSString stringWithString:a];NSString *f = [NSString stringWithFormat:@"f"];NSString *string1 = [NSString stringWithFormat:@"abcdefg"];NSString *string2 = [NSString stringWithFormat:@"abcdefghi"];NSString *string3 = [NSString stringWithFormat:@"abcdefghij"];}return 0;
}
a: 0x100002038, __NSCFConstantString, 18446744073709551615
b: 0x10071f3c0, __NSCFString, 1
c: 0x100002038, __NSCFConstantString, 18446744073709551615
d: 0x6115, NSTaggedPointerString, 18446744073709551615
e: 0x100002038, __NSCFConstantString, 18446744073709551615
f: 0x6615, NSTaggedPointerString, 18446744073709551615
string1: 0x6766656463626175, NSTaggedPointerString, 18446744073709551615
string2: 0x880e28045a54195, NSTaggedPointerString, 18446744073709551615
string3: 0x10071f6d0, __NSCFString, 1 */

为Tagged Pointer的有d、f、string1、string2指针。它们的指针值分别为0x6115、0x6615 、0x6766656463626175、0x880e28045a54195。
其中0x61、0x66、0x67666564636261分别对应字符串的 ASCII 码。
最后一位5的二进制为0101,最后一位1是代表这个指针是Tagged Pointer,010对应十进制为2,表示NSString类。
倒数第二位1、1、7、9代表字符串长度

在这里插入图片描述

NSString的类型NSString类型

注意: MacOS与iOS平台下的Tagged Pointer有差别:

MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标识位
iOS下则采用MSB(Most Significant Bit,即最高有效位)为Tagged Pointer标识位。

下图是iOS下NSNumber的Tagged Pointer位视图: Tagged Pointer 位视图

在这里插入图片描述

下图是iOS下NSString的Tagged Pointer位视图:

在这里插入图片描述

相关题目

执行以下两段代码,有什么区别?

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);for (int i = 0; i < 1000; i++) {dispatch_async(queue, ^{self.name = [NSString stringWithFormat:@"abcdefghij"];});}
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);for (int i = 0; i < 1000; i++) {dispatch_async(queue, ^{self.name = [NSString stringWithFormat:@"abcdefghi"];});}

第一段代码会报错

第一段代码中self.name__NSCFString类型,而第二段代码中为NSTaggedPointerString类型。__NSCFString存储在堆上,它是个正常对象,需要维护引用计数的。self.name通过setter方法为其赋值。而setter方法的实现如下:

- (void)setName:(NSString *)name {if(_name != name) {[_name release];_name = [name retain]; // or [name copy]}
}

我们异步并发执行setter方法,可能就会有多条线程同时执行[_name release],连续release两次就会造成对象的过度释放,导致Crash。

解决办法:

  • 使用atomic属性关键字。
  • 加锁

而第二段代码中的NSString为NSTaggedPointerString类型,在objc_release函数中会判断指针是不是TaggedPointer类型,是的话就不对对象进行release操作,也就避免了因过度释放对象而导致的Crash,因为根本就没执行释放操作。

objc_release(id obj)
{if (!obj) return;if (obj->isTaggedPointer()) return;return obj->release();
}

二,内存对齐

在iO64位系统中,采用8字节对齐(计算属性内存空间大小总和),最小内存大小为16个字节,实际分配空间是16字节对齐。

在计算机中,内存大小的基本单位是字节,理论上可以在任意地址在访问某种基本数据类型。而计算机并非按早字节大小读写内存,而是以2,4,8的字节块来读写内存。因此,编译器会对基本数据类型的合法地址做出一些限制,地址必须是2,4,8的倍数。那么就要求各种数据类型按早一定的规则在空间上排列,这就是内存对齐

对象的属性内存布局遵循下面规则:

  • 结构体变量的首地址是其最长基本类型成员的整数倍
  • 结构体的总大小为结构体最大基本类型成员变量的整数倍
  • 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如不满足,对前一个成员填充字节以满足
  • 如果一个结构体内部成员变量包括其他结构体成员,则结构体成员要从其内部成员最大元素大小的整数倍地址开始储存
  • 结构体中的成员变量都是分配在连续的内存空间中
  • 结构体成员顺序不同,会导致所占内存空间不一样;对象经过编译器优化,就不会有这个问题

实例:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person.h"
int main(int argc, const char * argv[]) {@autoreleasepool {//person 有name,age属性//如果对象创建了没去赋值属性,它会是内存假地址Person* person = [[Person alloc] init];person.name = @"111";person.age = 20;//class_getInstanceSize依赖于<ojc/runtime.h>返回创建一个实例对象的内存大小就是获取对象的全部属性的大小NSLog(@"class_getInstanceSize = %zd", class_getInstanceSize([Person class]));//输出24//malloc_size依赖于<malloc/malloc.h>返回给系统分配给对象的内存大小,而且最小是16字节。就是获取对象的全部属性的大小总和,然后按8位对齐获得,不足8位补齐8位。        NSLog(@"malloc_size = %zd", malloc_size((__bridge  const void*)person));//输出32//最后 sizeOf 得到的内存大小都是8个字节, 是因为 sizeOf获取的是类型所分配内存,所传参数为指针类型,所以最后得到的都是8NSLog(@"sizeof  = %zd", sizeof(person));//输出8}return 0;
}
  • class_getInstance 获取实例对象在内存对齐的情况下,所占大小
  • malloc_size 获取的是实际系统所分配的内存大小
  • sizeOf 获取类型所占字节大小,如果传的是对象,永远都是8;

内存对齐的原因

  • 性能上的提升
    从内存的占用的角度来讲,对齐后比未对齐有些情况反而增加了内存分配的开支。数据结构(尤其是栈)应该静可能在自然边界对齐,为了访问为对齐的内存,处理器会进行两次的内存访问;而对齐的内存访问仅需要一次的访问,最重要提高了内存系统的性能。
  • 跨平台
    某些硬性的平台不能访问任意地址上的任意数据的,只能 处理特定类型的数据,否则会导致硬件基基层的错误。

注意:

如果给类添加方法,类实例对象内存大小是不会变化的,为什么那?
创建对象的时候并不会给对象的方法分配内存,只会给属性,成员变量分配内存。一个类可能创建多个实例,每个实例的方法都一样,没有差异性,所有对象共用这块存储方法的内存,实际上方法都存储在类实例里面了,一个类只有一个类实例,由系统创建。这么设计的好处就是节省空间,加快初始化速度等

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

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

相关文章

240717.LeetCode——2974.最小数字游戏

题目描述 你有一个下标从 0 开始、长度为 偶数 的整数数组 nums &#xff0c;同时还有一个空数组 arr 。Alice 和 Bob 决定玩一个游戏&#xff0c;游戏中每一轮 Alice 和 Bob 都会各自执行一次操作。游戏规则如下&#xff1a; 每一轮&#xff0c;Alice 先从 nums 中移除一个 …

转移C盘中的conda环境(包括.condarc文件修改,environment.txt文件修改,conda报错)

conda环境一般是默认安装到C盘的&#xff0c;若建立多个虚拟环境&#xff0c;时间长了&#xff0c;容易让本不富裕的C盘更加雪上加霜&#xff0c;下面给出将conda环境从C盘转移到D盘的方法。 目录 电脑软硬件转移方法查看当前conda目录转移操作第一步&#xff1a;.condarc文件修…

Apache Flink 入门

零、概述 Apache Flink 是一个高性能的开源分布式流处理框架&#xff0c;专注于实时数据流的处理。 它设计用于处理无界和有界数据流&#xff0c;在内存级速度下提供高效的有状态计算。 Flink 凭借其独特的Checkpoint机制和Exactly-Once语义&#xff0c;确保数据处理的准确性…

只用 CSS 能玩出什么花样?

在前端开发领域&#xff0c;CSS 不仅仅是一种样式语言&#xff0c;它更像是一位多才多艺的艺术家&#xff0c;能够创造出令人惊叹的视觉效果。本文将带你探索 CSS 的无限可能&#xff0c;从基本形状到动态动画&#xff0c;从几何艺术到仿生设计&#xff0c;只用 CSS 就能玩出令…

Vscode中Github copilot插件无法使用(出现感叹号)解决方案

1、击扩展或ctrl shift x ​​​​​​​ 2、搜索查询或翻找到Github compilot 3、点击插件并再左侧点击登录github 点击Sign up for a ... 4、跳转至github登录页&#xff0c;输入令牌完成登陆后返回VScode 5、插件可以正常使用

微服务实战系列之玩转Docker(三)

前言 镜像&#xff08;Image&#xff09;作为Docker的“水源”&#xff0c;取之于它&#xff0c;用之于它。这对于立志成为运维管理的撒手锏——Docker而言&#xff0c;重要性不言而喻。 我们在虚拟机时代&#xff08;当然现在依然ing…&#xff09;&#xff0c;如何快速完成…

成为CMake砖家(5): VSCode CMake Tools 插件基本使用

大家好&#xff0c;我是白鱼。 之前提到过&#xff0c;白鱼的主力 编辑器/IDE 是 VSCode&#xff0c; 也提到过使用 CMake Language Support 搭配 dotnet 执行 CMakeLists.txt 语法高亮。 对于阅读 CMakeLists.txt 脚本&#xff0c; 这足够了。 而在 C/C 开发过程中&#xff…

NXP i.MX8系列平台开发讲解 - 3.19 Linux TTY子系统(二)

专栏文章目录传送门&#xff1a;返回专栏目录 Hi, 我是你们的老朋友&#xff0c;主要专注于嵌入式软件开发&#xff0c;有兴趣不要忘记点击关注【码思途远】 目录 1. Linux 串口驱动 1.1 Uart 驱动注册流程 1.2 uart 操作函数 1.3 line discipline 2. Linux tty应用层使用…

FPGA 实现DDR4的读写

1 硬件设计 FPGA 端&#xff1a; DDR4: 2 验证方案 3 仿真验证 4 DDR4 下板验证

《昇思25天学习打卡营第25天|第10天》

今天是打卡的第十天&#xff0c;今天开始学应用实践中的LLM原理和实践&#xff0c;今天学的是基于MindSpore实现BERT对话情绪识别。最先了解的是BERT模型的简介&#xff08;来自变换器的双向编码器表征量&#xff08;Bidirectional Encoder Representations from Transformers&…

NodeJS技巧:在循环中管理异步函数的执行次数

背景介绍 在现代Web开发中&#xff0c;NodeJS因其高效的异步处理能力而备受青睐。尤其在数据抓取、网络爬虫等应用场景中&#xff0c;NodeJS的非阻塞I/O特性使其成为不二之选。然而&#xff0c;在实际编程过程中&#xff0c;我们经常会遇到一个棘手的问题——如何在循环中控制…

各地跨境电子商务示范区工具变量DID数据(2010-2022年)

数据来源&#xff1a;参考李震等&#xff08;2023&#xff09;的做法&#xff0c;从官方网站上搜集整理了我国跨境电子商务示范区名单与上市公司进行匹配制作。时间跨度&#xff1a;2010-2022年数据范围&#xff1a;上市企业包含指标&#xff1a; stock year 证券简称 In…

个性化画册制作方法,快来看看

画册&#xff0c;不仅仅是一本书&#xff0c;它是记忆的宝库&#xff0c;是时光的缩影。随着技术的进步&#xff0c;个性化画册的制作已经不再是一件困难的事情。今天&#xff0c;就让我来为大家揭开个性化画册制作的神秘面纱&#xff0c;带你一起创造独一无二的回忆。 第一步&…

数据库内核研发学习之路(三)创建postgres内置函数

本章之前已经讲明白了我们的postgres如何进行编译安装&#xff0c;这是很重要的一步&#xff0c;接下来就是学会对postgres进行小的改动&#xff0c;然后保证依然能够顺利编译安装运行&#xff01; 本章续讲内容如何创建一个内置函数。 1、内置函数和用户自定义函数的区别 熟…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【查询密钥别名集(ArkTS)】

查询密钥别名集(ArkTS) HUKS提供了接口供应用查询密钥别名集。 开发步骤 初始化密钥属性集。用于查询指定密钥别名集TAG&#xff0c;TAG仅支持HUKS_TAG_AUTH_STORAGE_LEVEL。调用接口[listAliases]&#xff0c;查密钥别名集。 HarmonyOS与OpenHarmony鸿蒙文档籽料&#xff1…

入门小结:JavaScript小白语法

一、变量 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…

Boost中线程的使用

目录 boost的线程基本用法 boost:condition thread_group 线程组 thread_pool boost的线程基本用法 boost::thread Thread_GenerateUuid;boost::thread Thread_ShowUuid;boost::mutex mutex;std::queue<std::string>UuidQueue;void procGenerateUuid();void showUuid…

vmware_虚拟机安装zabbix_超快超简单

TIPS: 一开始用docker 和 安装包&#xff0c;安装zabbix总是有问题&#xff0c;后发现zabbix官方提供了装好的虚拟机 1、下载VMware pro 个人免费版 官网地址如下 https://support.broadcom.com/group/ecx/productdownloads?subfamilyVMwareWorkstationPro 如果提示注册&am…

服务器数据恢复—开盘修复raid5阵列硬盘故障的数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌P2000存储&#xff0c;存储中有一组由8块硬盘&#xff08;包含一块热备盘&#xff09;组建的raid5阵列。上层部署VMWARE ESX虚拟化平台。 服务器存储故障&#xff1a; 存储在运行过程中有两块硬盘指示灯亮黄色。经过运维人员的初步检…

Go语言中GC(垃圾回收回收机制)三色标记与混合写屏障

5、Golang三色标记混合写屏障GC模式全分析 (yuque.com) 第1讲-课程目标_哔哩哔哩_bilibili Golang三色标记GC混合写屏障 Go V1.3之前的标记清除&#xff08;mark and sweep) 垃圾回收、内存管理、自动适放、三色标记法、STW (stop the world) 图的遍历&#xff1f;可达性分…