c++内存对齐

原文在这里。https://blog.csdn.net/WangErice/article/details/103598081

但是内容有错误。我在自己的这里修改并变成红色了。

内存在使用过程并不是单一的依次排列,而是按照某种既定的规则来进行对齐,以方便快速访问.内存的对齐原则有以下三条:

数据成员对齐:成员根据其自身大小,从自身大小的整数倍内存地址(以第一个元素存储在0位置为参考)开始存储;
结构体成员对齐:如果包含了结构体成员,则结构体成员的存储位置从其内部成员最大值的整数倍地址开始存储;
结构体总大小对齐:必须要是其内部最大成员的整数倍,不足的要补齐.
注:以下内容在arm64指令集设备做演示.

数据成员对齐
在存储过程中,第一个成员变量会默认从相对位置为offset=0处开始存储,下一个成员的存储,要存储在其类型所需内存的整数倍地址位置处.

struct One{
    char a; //由于是第一个成员,且char类型需要一个字节存储数据,所以成员a存储在[0]处,占据一个字节;
    int b; //由于int类型需要四个字节存储数据,所以存储位置需要是4的整数倍,所以需要存储在[4]处,占据四个字节,也就是说成员b的存储占据了4-7四个字节的内存空间,所以a,b共占用了八个字节
}
使用Xcode初始化一个One类型的结构体变量:

One one = {'a', 12};
使用以下指令查看内存分布:

(lldb) po &one
0x000000016b31d3b8
 
(lldb) x/2wx 0x000000016b31d3b8
0x16b31d3b8: 0x00000061 0x0000000c //0x00000061=97存储成员'a', 0x0000000c=12存储成员b
可以看到由于数据成员对齐原则的影响,成员变量b从第五个字节开始存储,所以虽然第一个成员变量a只需要一个字节的存储空间,但为了字节对齐,之后的三个字节空间是空出来的,也可以理解为内存使用了四个字节来存储成员变量a.

结构体成员对齐
如果包含了结构体成员,则结构体成员的存储位置从其内部成员最大值的整数倍地址开始存储.

struct Two {
    short c; //2个字节,存储起始地址0开始,占用两个字节
    One d; //由于结构体One中的成员变量有两个类型的成员变量char(1个字节),int(4个字节),所以One需要从4处开始存储,占据5个字节,所以c,d共占用了12个字节。原文这里写的是9。写错了。
};
初始化一个Two类型的结构体变量:

    One one = {'a', 12};
    Two two = {
        20,
        one
    };
使用一下指令查看内存分布:

(lldb) po &two
0x000000016d3953a8
 
(lldb) x/4wx 0x000000016d3953a8
0x16d3953a8: 0x9f750014 0x0000000c 0x00000061 0x00000000
可以看到由于结构体One中最大的成员变量是int类型(4字节),所以成员变量d在内存中从第四个字节的位置开始存储,占据八个字节.

结构体总大小对齐
结构体总体占据内存的大小必须要是其内部最大成员的整数倍,不足的要补齐.所以,在结构体Three中

struct Three {
    int e;//从0处开始存储,占据4个字节
    char f; //根据[数据成员对齐]规则,从5处开始存储占据一个字节
}
初始化一个Three类型的结构体变量:

Three three = {12, 'a'};
根据数据成员对齐和结构体成员对齐原则,结构体变量three需要5个字节进行存储,但是根据结构体总大小对齐原则,结构体变量three需要补齐三个字节以满足结构体大小为(int 占据4个字节)的整数倍.所以该结构体变量需要八个字节空间进行存储.

根据以上原则,以下结构体占据的字节空间为:

struct Four
{
     int id;             //[0]....[3]
     double weight;      //[8].....[15]      数据成员对齐原则
     float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     结构体总大小对齐原则
};
 
所以Four结构体变量的存储需要24个字节
 
 
 
 
struct Five {
    int a;
}
 
struct Six {
    char c; //[0]
    Five d; //[4]...[7]     成员变量对齐原则
    char e; //[8],总长为4的倍数,需要补齐[9],[10], [11]        结构体总大小对齐原则
};
 
所以Six类型的成员变量的存储需要12个字节
 
 
 
typedef Seven {
    int a;  //[0]-[3]
    short b[2]; // [4]-[7]          成员变量对齐原则
                //需要补齐最后一位     结构体总大大小对齐原则
}seven;
 
所以Seven类型的成员变量的存储需要8个字节
 
 
typedef struct {
    int a; //[0]--[3]
    char b[3]; //[4],[5],[6]     成员变量对齐原则
    Seven c; //[8]-[15]          成员变量对齐原则
             //需要补齐最后一位    结构体总大大小对齐原则
}Eight;
 
所以Eight类型的成员变量的存储需要16个字节
 
 
 
而结构体在很大程度上其实是类的原型,所以类的内存分布与结构体相似的,只不过在OC的类中,默认有一个isa的存在,所以第一个成员变量都是isa.

@interface EWPerson : NSObject
//隐藏的成员变量isa                            8个字节
@property (copy, nonatomic) NSString *name;  //8个字节
@property (assign, nonatomic) NSInteger age; //8个字节
@property (assign, nonatomic) NSInteger height; //8个字节
 
@end
 
@implementation EWPerson
 
@end
所以EWPerson类型的对象需要32个字节,其中:

isa:8个字节;
name:8个字节;
age:8个字节;
height:8个字节;
初始化一个EWPerson类型的对象:

    EWPerson *person = [[EWPerson alloc] init];
    person.age = 12;
    person.name = [NSString stringWithFormat:@"%@", @"abcd"];
    person.height = 175;
使用LLDB查看一下内存分布:

(lldb) po person
<EWPerson: 0x280bd2aa0>
 
(lldb) x/4gx 0x280bd2aa0
0x280bd2aa0: 0x000001a1028a94c5 0xfd97df07a4b040aa
0x280bd2ab0: 0x000000000000000c 0x00000000000000af
跟预期一样,其中name对应的0xfd97df07a4b040aa是一个tagged pointer指针.

如果将将age修改为int类型,而height修改为double类型,根据成员变量对齐原则,height需要从相对位置[24]开始存储,所以age虽然需要四个字节,但是紧随其后的四个字节需要填充0来对齐:

@interface EWPerson : NSObject
//隐藏的成员变量isa                            8个字节
@property (copy, nonatomic) NSString *name; //8个字节(可能是真实的地址指针,也可以是tagged pointer类型指针)
@property (assign, nonatomic) int age;      //4个字节
@property (assign, nonatomic) double height; //8个字节
 
@end
 
@implementation EWPerson
 
@end
初始化一个EWPerson类型的对象:

    EWPerson *person = [[EWPerson alloc] init];
    person.age = 12;
    person.name = [NSString stringWithFormat:@"%@", @"abcd"];
    person.height = 175;
使用LLDB查看内存分布:

(lldb) po person
<EWPerson: 0x2809192a0>
 
(lldb) x/4gx 0x2809192a0
0x2809192a0: 0x000001a10099d4c5 0x000000000000000c
0x2809192b0: 0xf4b9afbebc95968e 0x4065e00000000000
发现:

本来应该在第二个位置的那么属性跑到了第三个位置,而本来在第三个位置的age跑到了第二个位置
这是为啥呢?其实也很好理解,这是编译器做了优化,将成员变量按照所需要字节的大小依次排列,这样小的字节就有可能会组合在一起构成一个构成一个大的成员变量所需要的字节,从而达到节约内存的目的.

例如:

struct Nine {
    short a; //2个字节,[0]-[1]
    int b; //4个字节,根据成员变量对齐原则,需要从[4]开始存储,即存储占据[4]-[7]四个字节
    short c; //2个字节,[8]-[9]
}
最终,根据整体大小对齐原则,结尾需要补齐2个字节该结构体的实例需要12个字节来进行存储.然后同样的的成员变量,进行位置优化之后:
 
struct Nine {
    short a; //2个字节,[0]-[1]
    short c; //2个字节,[2]-[3]
    int b; //4个字节,根据成员变量对齐原则,需要从[4]开始存储,即存储占据[4]-[7]四个字节
}
此时只需要8个字节就可以存储该结构的实例.
也可以通过clang命令进行重写:

xcrun -sdk iphonesimulator clang -rewrite-objc main.m
在新生成的.cpp文件中查找到类对应的结构体:

#ifndef _REWRITER_typedef_EWPerson
#define _REWRITER_typedef_EWPerson
typedef struct objc_object EWPerson;
typedef struct {} _objc_exc_EWPerson;
#endif
 
extern "C" unsigned long OBJC_IVAR_$_EWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_EWPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_EWPerson$_height;
struct EWPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    NSString * _Nonnull _name;
    double _height;
};
可见,编译器确实对类对应的结构体进行了优化,主要的目的就是合理利用空间,节约内存.主要的方法就是将成员变量按照所需字节空间大小生序排列,尽可能将小的字节空间组合成单位字长进行存储.

第三个位置的tagged pointer和之前的值不一样
这很正常,每次的结果都有可能不一致,因为这个结果是真实值异或了一个随机数(objc_debug_taggedpointer_obfuscator)得到的,所以每次启动产生的随机数不一样,就会导致该结果不一致.详情可以查看tagged pointer.

最后一个值(0x4065e00000000000)是啥?
根据分析当然是成员变量height啦!因为在64bit操作系统中,double类型占据八个字节,其中符号位1位,指数位11位,尾数部分52位(参考浮点数在内存中的存储).

0x4065e00000000000 = 0b0100000001100101111000000000000000000000000000000000000000000000
其中:
最高位符号位:0,即表示正数;
接下来的11位表示指数位:0b10000000110减去1023(2^10-1)=0b00000000111=7
接下里的52位位位数:0b0101111000000000000000000000000000000000000000000000
所以表示的双精度浮点数为:
0b1.0101111000000000000000000000000000000000000000000000 * 2^7
=0b1.0101111 * 2^7
=0b10101111
=175
 
原文链接:https://blog.csdn.net/WangErice/article/details/103598081

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

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

相关文章

深度学习与python theano

文章目录 前言1.人工神经网络2.计算机神经网络3.反向传播4.梯度下降-cost 函数1.一维2.二维3.局部最优4.迁移学习 5. theano-GPU-CPU theano介绍1.安装2.基本用法1.回归2.分类 3.function用法4.shared 变量5.activation function6.Layer层7.regression 回归例子8.classificatio…

【JavaScript】读取本地json文件并绘制表格

本文为避免跨域问题&#xff0c;使用了改造过的本地json文件的方法实现读取json数据并绘制表格。 如果发起http请求获取本地 json文件中数据&#xff0c;需要架设本地服务器&#xff0c;本文不做阐述。 概述 1、json在本地&#xff0c;并不需要从服务器下载。 2、采用jquery…

国庆作业day5

应用层&#xff1a;提供用户与网络应用程序之间的接口。表示层&#xff1a;负责数据的格式转换、加密和解密。会话层&#xff1a;负责建立、管理和终止会话。它提供会话控制和同步&#xff0c;允许应用程序之间建立连接和交换数据。传输层&#xff1a;提供端到端的连接。网络层…

postgresql-管理数据表

postgresql-管理数据表 创建表数据类型字段约束表级约束模式搜索路径 修改表添加字段删除字段添加约束删除约束修改字段默认值修改字段数据类型重命名字段重命名表 删除表 创建表 在 PostgreSQL 中&#xff0c;使用 CREATE TABLE 语句创建一个新表&#xff1a; CREATE TABLE …

专业PDF编辑阅读工具PDF Expert mac中文特点介绍

PDF Expert mac是一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 PDF Expert mac软件特点 PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动、旋转、缩放、裁剪等操作…

树莓派4B与STM32串口通信

目录 2上篇文章的补充 2.1 树莓派通信设置 3树莓派与STM32通信 3.1接线准备 3.2代码 3.2.1 STM32代码&#xff1a; 3.2.2树莓派代码&#xff1a; 2上篇文章的补充 2.1 树莓派通信设置 在上篇文章的基础上&#xff0c;进一步的设置 终端输入&#xff1a;sudo minicom …

v-for中的key

在Vue中&#xff0c;当使用v-for指令循环渲染元素时&#xff0c;添加:key是一个推荐做法&#xff0c;尤其是在循环的元素可能会被重新排序、添加或删除的情况下。 :key的作用是为每个循环的元素提供一个唯一的标识符&#xff0c;以便Vue能够跟踪和管理这些元素的状态。Vue使用…

从 0 到 1 ,手把手教你编写《消息队列》项目(Java实现) —— 核心类持久化存储

文章目录 一、持久化存储的方式与路径二、公共模块序列化 / 反序列化异常规定 三、持久化存储数据库数据管理文件数据管理读写规定新增 /删除规定内存中 Message 的规定存储规定代码编写 硬盘数据管理 一、持久化存储的方式与路径 交换机,队列,绑定关系,这些我们使用数据库来管…

四、浏览器渲染过程,DOM,CSSDOM,渲染,布局,绘制详细介绍

知识点&#xff1a; 1、为什么不能先执行 js文件&#xff1f;&#xff1f; 我们不能先执行JS文件&#xff0c;必须等到CSSOM构建完成了才能执行JS文件&#xff0c;因为前面已经说过渲染树是需要DOM和CSSOM构建完成了以后才能构建&#xff0c;而且JS是可以操控CSS样式的&#…

编程前置:处理Excel表格,定位单元格位置,输入文字前,让AI机器人知道我说什么

原提问&#xff1a; input输入表头 &#xff08;input内除了/&#xff0c;空格 回车 标点符号等 全部作为单元格分隔符&#xff09; 由我设置input输入的是行or列 给选项 1. 行 2. 列 默认回车或没输入值是列由我设置起始位置行列 例如 3,2 表示3行2列 当我输入3,2 就表示在第…

【Java】继承练习

继承的思想实现猫和狗的案例&#xff0c;并在测试类中进行测试 猫和狗——共同特性都是属于动物 1. 定义动物类&#xff08;Animal&#xff09; 成员变量&#xff1a;姓名&#xff0c;年龄构造方法&#xff1a;无参&#xff0c;带参成员方法&#xff1a;get/set 方法 2. 定义猫…

springboot的配置文件(properties和yml/yaml)

springboot的配置文件有两种格式分别是properties和yml/yaml 创建配置文件 在创建springboot项目时候&#xff0c;会默认生成application.properties这种格式 书写风格 端口 application.propertis server.port8080 application.yml server:port: 8080 连接数据库 applica…

<Xcode> Xcode IOS无开发者账号打包和分发

关于flutter我们前边聊到的初入门、数据解析、适配、安卓打包、ios端的开发和黑苹果环境部署&#xff0c;但是对于苹果的打包和分发&#xff0c;我只是给大家了一个链接&#xff0c;作为一个顶级好男人&#xff0c;我认为这样是对大家的不负责任&#xff0c;那么这篇就主要是针…

【计算机网络黑皮书】应用层

【事先声明】 这是对于中科大的计算机网络的网课的学习笔记&#xff0c;感谢郑烇老师的无偿分享 书籍是《计算机网络&#xff08;自顶向下方法 第6版&#xff09;》 需要的可以私信我&#xff0c;无偿分享&#xff0c;课程简介下也有 课程连接 目录 应用层网络应用的原理应用架…

作业 day4

完成父子进程通信

Socket通信

优质博文IT-BLOG-CN 一、简介 Socket套接字&#xff1a;描述了计算机的IP地址和端口&#xff0c;运行在计算机中的程序之间采用socket进行数据通信。通信的两端都有socket&#xff0c;它是一个通道&#xff0c;数据在两个socket之间进行传输。socket把复杂的TCP/IP协议族隐藏在…

Linux基本指令(二)

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

接着聊聊如何从binlog文件恢复误delete的数据,模拟Oracle的闪回功能

看腻了文章就来听听视频演示吧&#xff1a;https://www.bilibili.com/video/BV1cV411A7iU/ delete忘加where条件&#xff08;模拟Oracle闪回&#xff09; 操作基本等同于上篇&#xff1a;再来谈谈如何从binlog文件恢复误update的数据&#xff0c;模拟Oracle的回滚功能 原理&a…

LCR 056.两数之和 IV

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;LCR 056. 两数之和 IV - 输入二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 广度优先遍历二叉树的同时将已遍历过的节点值加入哈希表&#xff0c;若目标值与当前节点值之差存在…

java部分常见错误示例

Java中较为复杂和常见的错误示例&#xff0c;包括运行后的错误信息以及修复方法&#xff1a; 1. 空指针异常&#xff08;NullPointerException&#xff09; String text null; int length text.length(); // 运行后会抛出 NullPointerException错误信息&#xff1a; Exce…