当前位置: 首页 > news >正文

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

相关文章:

c++内存对齐

原文在这里。https://blog.csdn.net/WangErice/article/details/103598081 但是内容有错误。我在自己的这里修改并变成红色了。 内存在使用过程并不是单一的依次排列&#xff0c;而是按照某种既定的规则来进行对齐&#xff0c;以方便快速访问.内存的对齐原则有以下三条&#…...

leetcode 33. 搜索旋转排序数组

2023.9.26 本题暴力法可以直接A&#xff0c;但是题目要求用log n的解法。 可以想到二分法&#xff0c;但是一般二分法适用于有序数组的&#xff0c;这里的数组只是部分有序&#xff0c;还能用二分法吗&#xff1f; 答案是可以的。因为数组是经过有序数组旋转得来的&#xff0c;…...

VCS flow学习

VCS VCS 是IC从业者常用软件&#xff0c;该篇文章是一个学习记录&#xff0c;会记录在使用过程中各种概念及options。 VCS Flow VCS Flow 可以分为Two-step Flow和Three-step Flow两类。 两步法 两步法只支持Verilog HDL和SystemVerilog的design&#xff0c;两步法主要包括…...

微信扫码关注公众号登录功能php实战分享

1、安装easywechat 基于easywechat框架开发,首先下载安装easywechat composer require overtrue/wechat 2、公众号配置 先去公众号后台基本配置/ 填写服务器配置配置接口,需要是线上能正确收到微信推送消息的地址,关注如果有关注、扫码、收到消息等事件都会推送到该地址…...

Git 精简快速使用

安装 Git 忽略&#xff0c;自行搜索 新建项目&#xff0c;或者在仓库拉取项目&#xff0c;进入到项目目录 Github 给出的引导&#xff0c;新项目和旧项目 echo "# testgit" >> README.md git init git add README.md git commit -m "first commit"…...

线性约束最小方差准则(LCMV)波束形成算法仿真

常规波束形成仅能使得主波束对准目标方向&#xff0c;从而在噪声环境下检测到目标&#xff0c;但无法对复杂多变的干扰做出响应&#xff0c;所以不能称之为真正意义上的自适应滤波。自适应阵列处理指的是采用自适应算法对空间阵列接收的混合信号进行处理&#xff0c;又可称为自…...

什么是内容运营?

关于内容运营&#xff0c;在不同种类的公司&#xff0c;侧重点也不一样。 电商平台的内容运营岗更偏内容营销&#xff1b;产品功能比较简单的公司&#xff0c;内容运营和新媒体运营的岗位职责差不多&#xff1b;而内容平台的内容运营更多的是做内容的管理和资源整合。...

搭建安信可小安派Windows 开发环境

搭建小安派Windows 开发环境 Ai-Pi-Eyes 系列是安信可开源团队专门为Ai-M61-32S设计的开发板&#xff0c;支持WiFi6、BLE5.3。所搭载的Ai-M61-32S 模组具有丰富的外设接口&#xff0c;具体包括 DVP、MJPEG、Dispaly、AudioCodec、USB2.0、SDU、以太网 (EMAC)、SD/MMC(SDH)、SP…...

XML文件反序列化读取

原始XML文件 <?xml version"1.0" encoding"utf-8" ?> <School headmaster"王校长"><Grade grade"12" teacher"张老师"><Student name"小米" age"18"/><Student name&quo…...

会议剪影 | 思腾合力受邀参加2023第二届世界元宇宙大会并作主题演讲

由中国仿真学会、中国指挥与控制学会和北京理工大学共同主办&#xff0c;上海市嘉定区安亭镇人民政府和中国仿真学会元宇宙专业委员会承办的第二届世界元宇宙大会于2023年9月20日-22日在上海安亭举行。 大会以“虚实相生、产业赋能”为主题&#xff0c;聚焦元宇宙关键技术发展的…...

加密算法、哈希算法及其区别+国密简介

现代加密算法是信息安全领域中常用的算法&#xff0c;用于保护数据的机密性和完整性。以下是一些常用的现代加密算法&#xff1a; 加密算法&#xff08;Encryption Algorithm&#xff09; 目标&#xff1a;加密算法的主要目标是保密性&#xff08;Confidentiality&#xff09;…...

LeetCode算法二叉树—222. 完全二叉树的节点个数

目录 222. 完全二叉树的节点个数 - 力扣&#xff08;LeetCode&#xff09; 代码&#xff1a; 运行结果&#xff1a; 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能…...

Scrapy-应对反爬虫机制

参考自https://blog.csdn.net/y472360651/article/details/130002898 记得把BanSpider改成自己的项目名&#xff0c;还有一个细节要改一下&#xff0c;把代码user换成user_agent 禁止Cookie 在Scrapy项目中的settings文件&#xff0c;可以发现文件中有以下代码: COOKIES_ENA…...

Direct3D字体

D3DX库提供接口ID3DXFont用于在Direct3D应用程序中绘制文本&#xff0c;该接口内部使用GDI(图形设备接口)来绘制文本&#xff0c;因此该接口在性能上略有损失&#xff0c;由于使用GDI所以才能够处理一些复杂的字体和格式。可以用D3DXCreateFontIndirect函数来创建一个ID3DXFont…...

麒麟软件操作系统下载

银河麒麟高级服务器操作系统V10&#xff08;鲲鹏版&#xff09;&#xff1a; https://distro-images.kylinos.cn:8802/web_pungi/download/share/yYdlHoRzAre1mFPK9s3NviID4Lg5w6MW/ 银河麒麟高级服务器操作系统V10&#xff08;飞腾版&#xff09;&#xff1a; https://dist…...

ARM---实现1-100求和任务

.text .globl _start_start:mov r0, #0x1mov r1, #0x1 给r1加一固定1不变mov r2, #0x64 100判断bl sumcmp r1, r2 sum:addcc r1, r1,#0x1 r1自增addcc r0, r0, r1 r0求和movcc pc,lrstop:b stop.end...

Vue+Three.js实现三维管道可视化及流动模拟续集

继上一篇文章中实现了三维管道的可视化和流动模拟,经过反馈,对大家还是有一定帮助,因此就编写了一个续集,相当于增加了一些常见的通用共性功能,主要在前面的基础上增加了以下功能:1.新增直角拐弯的管道,工业中很多管道都是横平竖直的,相当于我们装修的水管或电线等,不…...

基于Xilinx UltraScale+ MPSOC(ZU9EG/ZU15EG)的高性能PCIe数据预处理平台

PCIE707是一款基于PCIE总线架构的高性能数据预处理FMC载板&#xff0c;板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;1路PCIe x4主机接口、1个RJ45千兆以太网口、2个QSFP 40G光纤接口。板卡采用Xilinx的高性能UltraScale MPSOC系列FPGA作为实时处理器&#xff0c…...

IMX6ULL ARM Linux开发板SD卡启动,SD卡的分区与分区格式化创建

一、确定TF卡挂载到ubuntu上的设备名称及分区情况 1. 在ubuntu不接入TF卡的情况下&#xff0c; 使用df -lh /dev/sd*命令查看当前"/dev/sd开头"的设备。 ##输入df -lh /dev/sd*命令&#xff0c;敲回车键 ~$ df -lh /dev/sd* 2.将TF卡接入到ubuntu&#xff0c;再次使…...

去哪里找图标?

2023年9月27日&#xff0c;周三下午 为了准备软件工程的期末项目&#xff0c;我需要找给项目找一些图标 目录 1、iconfont 2、material-design-icons 3、Font-Awesome 4、feather 5、Bootstrap 官方图标库 1、iconfont iconfont-阿里巴巴矢量图标库 这个需要注册&…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...