【C++】结构体内存对齐规则

news2024/10/6 12:18:42

一、结构体内存对齐(重要)

结构体内存对齐是结构体大小的计算规则,是校招笔试和面试过程中一个十分热门的考点,希望大家认真对待。

在学习结构体内存对齐之前,我们先给两组计算结构体大小的题目,看看你能否做对:

//计算结构体大小
#include <stdio.h>
struct S1 
{
char c1;
int i;
char c2;
};

struct S2 
{
char c1;
char c2;
int i;
};

int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}

在这里插入图片描述

我知道你很急,但你先别急,我们接着分析规则探索答案的由来。

结构体内存对齐的规则

关于结构体内存对齐规则,大部分参考资料是这样说的:

  1. 第一个成员在与结构体变量偏移量为0的地址处(第一个成员的地址)

  2. 其他成员变量对齐到它的对齐数的整数倍的地址处(决定非第一个成员与上一个成员地址的关系)eg: 一个值的对齐数是3,那么在地址图中,它只可以存放在0,3,6,9…

    • 对齐数 = 编译器默认的对齐数与该成员变量大小的较小值
    • VS的默认对齐数是8
    • 只有VS编译器下才有默认对齐数的概念,其他编译器下变量的对齐数 = 变量的大小
  3. 结构体总大小为最大对齐数的整数倍。(最大对齐数为所有变量的对齐数的最大值)

  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小为所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

现在我对其进行规则提纯:
a.先算出每一个变量的对齐数(并标注最大)
b.分类讨论不同成员的偏移量
c.计算总结构体的大小

知道了最大对齐数的对齐规则,我们再来看上面的练习题:

struct S1 
{
char c1;  //变量大小为1,默认对齐数为8 -> 对齐数为1
int i;    //变量大小为4,默认对齐数为8 -> 对齐数为4
char c2;  //变量大小为1,默认对齐数为8 -> 对齐数为1
//最大对齐数是4
};

分析过程:
我们假设struct S1的起始位置为图中箭头所示位置,则各位置的偏移量如图;由内存对齐的规则:
第一个成员在与结构体变量偏移量为0的地址处:所以c1在偏移量为0处,且c1占一个字节;

其他成员变量要对齐到它的对齐数的整数倍的地址处:由于 i 的对齐数是4,所以 i 只能从偏移量为4的位置开始存储,且 i 占四个字节;

其他成员变量要对齐到它的对齐数的整数倍的地址处:由于 c2 的对齐数是1,所以 c2 紧挨着 i 存储,且 c2 占一个字节;

结构体总大小为最大对齐数的整数倍:由于最大对齐数为4,所以总对齐数要为4的倍数,大于9的最小的4的倍数为12,所以整个结构体的大小为12个字节。
image-20220712180818425

struct S2 
{
char c1;  //变量大小为1,默认对齐数为8 -> 对齐数为1
char c2;  //变量大小为1,默认对齐数为8 -> 对齐数为1
int i;    //变量大小为4,默认对齐数为8 -> 对齐数为4
};

分析过程:
我们假设struct S2的起始位置为图中箭头所示位置,则各位置的偏移量如图;由内存对齐的规则:
第一个成员在与结构体变量偏移量为0的地址处:所以c1 从0偏移处开始,占一个字节;

其他成员变量要对齐到它的对齐数的整数倍的地址处:c2 对齐数为1,所以紧挨着 c1 存储,占一个字节;
其他成员变量要对齐到它的对齐数的整数倍的地址处:i 对齐数为4,所以在4的整数倍位置 – 4偏移处开始存储,占4个字节;

存放完毕后0~7一共占8个字节,因为最大对齐数为4,8为4的整数倍,所以不变
image-20220712181237786

二、offsetof 宏(求结构体偏移量)

offsetof 的介绍

offsetof 是C语言中定义的一个用于求结构体成员在结构体中的偏移量的一个宏,其对应的头文件是 <stddef.h>,由于 offsetof 的使用方法与函数一样,所以它经常被错误的认为是一个函数;我们可以在VS中右键单击offsetof转到定义,查看offsetof的在VS中的实现方式。
在这里插入图片描述

offsetof 的参数

size_t offsetof( 结构体变量名, 成员变量名 );

offsetof 的使用

#include <stdio.h>
#include <stddef.h>  //offsetof对应头文件
struct S1
{
char c1;
int i;
char c2;
};

struct S2
{
char c1;
char c2;
int i;
};

int main()
{
printf("%d\t", offsetof(struct S1, c1));
printf("%d\t", offsetof(struct S1, i));
printf("%d\n", offsetof(struct S1, c2));

printf("%d\t", offsetof(struct S2, c1));
printf("%d\t", offsetof(struct S2, c2));
printf("%d\n", offsetof(struct S2, i));

return 0;
}

在这里插入图片描述

这里offsetof的第一个参数写成S1 和 S2也是对的

offsetof 的模拟实现

我们以上面的 struct S1为例,经过上面的分析我们已经知道了 struct S1的大小为12,并且画出来具体的图示:image-20220712180818425

我们观察后发现:结构体成员在结构体中的偏移量 = 结构体成员的地址 - 结构体的起始地址,比如 struct S1中 i 的地址 - 结构体的起始地址可以得到结构体成员 i 的偏移量等于4;那么如果结构体的起始地址在0处,那么结构体成员的偏移量 = 结构体成员的地址 - 0 = 结构体成员地址,所以我们可以把0强转为对应结构体指针类型,然后返回结构体成员的地址即可得到结构体成员的偏移量,具体代码如下:

#include <stdio.h>
#define OFFSETOF(type, member) (size_t)&(((type*)0)->member)
struct S1
{
char c1;
int i;
char c2;
};

int main()
{
printf("%d\n", OFFSETOF(struct S1, c1));
printf("%d\n", OFFSETOF(struct S1, i));
printf("%d\n", OFFSETOF(struct S1, c2));
return 0;
}

image-20220719232345217

三、为什么存在内存对齐

从上面的例子我们可以看到,结构体内存对齐会浪费一定的内存空间,但是计算机不是要尽可能的做到不浪费资源吗?那为什么还要存在内存对齐呢?关于内存对齐存在的原因,大部分的参考资料是这样说的:

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于:为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。所以内存对齐能够提高访问效率
  3. 总体来说:结构体的内存对齐是拿空间来换取时间的做法。

这里我对原因中的第二点做一下解释:

大家都知道,我们的机器分为32位机器和64位机器,这里的32位和64位其实指的是CPU的位数,而CPU的位数对应着CPU的字长,而字长又决定着CPU读取数据时一次访问多大即空间,即一次读取几个字节,我们以32位机器为例:

image-20220712183831801如图,32位机器一次访问四个字节的大小,如果不存在内存对齐,那么要取出 i 中的数据需要两次读取,存在内存对齐则只需要读取一次

设计结构体的技巧

在了解了结构体的对齐规则之后,有没有一种方法能让我们在设计结构体的时候既满足对齐规则,又能尽量的节省空间呢?其实是有的,方法就是:让占用空间小的成员尽量集中在一起。就像的习题,我们把占用空间下的 c1 和 c2 放在一起,从而使得 struct S2 比 struct S1 小了四个字节。

四、修改默认对齐数

我们可以使用 “#pragma pack(num)” 命令来修改VS中的默认对齐数。例如:

#include <stdio.h>

#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}

image-20220712184907940

在 struct S2 中,我们通过 " #pragma pack(1) " 命令把VS的默认对齐数设置为1(相当于不对齐),使得其大小变为6。

五、结构体大小计算习题

习题1

#include <stdio.h>

struct S3
{
double d;
char c;
int i;
};

int main()
{
printf("%d\n", sizeof(struct S3));
return 0;
}

image-20220712185417651
妈了个巴子,double是8个字节,老糊涂了

d 从0偏移处开始存储,占8个字节,所以0~7;c 紧挨 d 存储,占一个字节,所以8,i 从4的整数倍即12处开始存储,占4个字节,所以12~15;所以0 ~ 15合计16个字节,16为最大对齐数8的倍数,所以不变。

习题2

#include <stdio.h>

struct S3
{
double d;
char c;
int i;
};

struct S4
{
char c1;
struct S3 s3;
double d;
};

int main()
{
printf("%d\n", sizeof(struct S4));
return 0;
}

c1 从0偏移位置开始存储,占一个字节,所以0;struct S3 s3 我们上面已经算出占16个字节,又因为嵌套的结构体对齐到自己的最大对齐数的整数倍处,所以从8的整数倍即8偏移处开始存储,所以8~23;d 从8的整数倍即24偏移处开始存储,占8个字节,所以24~31;合计32个字节,且为最大偏移数8的整数倍,所以不变。

习题3

#include <stdio.h>

#pragma pack(4)
struct tagTest1
{
    short a;
    char d;
    long b;
    long c;
};
struct tagTest2
{
    long b;
    short c;
    char d;
    long a;
};
struct tagTest3
{
    short c;
    long b;
    char d;
    long a;
};
#pragma pack()

int main(int argc, char* argv[])
{
    struct tagTest1 stT1;
    struct tagTest2 stT2;
    struct tagTest3 stT3;

    printf("%d %d %d", sizeof(stT1), sizeof(stT2), sizeof(stT3));
    return 0;
}

image-20220712190713050

这是每个变量存储的地址位数,快看看能不能对上!

stT1:

a: 0~1 d:2 b:4~7 c:8~11 合计:0~11 = 12(4的倍数);

stT2:

b:0~3 c:4~5 d:6 a:8~11 合计:0~11 = 12(4的倍数);

stT3:

c:0~1 b:4~7 d:8 a:12~15 合计:0~15 = 16(4的倍数);

芜湖!! 希望这篇结构体对齐规则可以给你带来帮助!

请添加图片描述

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

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

相关文章

口袋参谋:淘宝不限类目,透视竞品实时销量!快试试这个插件

​在运营一家店铺之前&#xff0c;可以先了解各类目宝贝的市场行情&#xff0c;及时掌握不同类目宝贝的价格、销售情况&#xff0c;根据需求制定出属于自己的营销策略。 【可跨类目竞店透视】功能&#xff1a; 支持一键获取任意店铺宝贝概况信息 【跨类目竞店透视】功能使用 …

WavJourney:进入音频故事情节生成世界的旅程

推荐&#xff1a;使用 NSDT场景编辑器快速搭建3D应用场景 若要正确查看音频生成的强大功能&#xff0c;请考虑以下方案。我们只需要提供一个简单的指令&#xff0c;描述场景和场景设置&#xff0c;模型就会生成一个扣人心弦的音频脚本&#xff0c;突出与原始指令的最高上下文相…

如何应对数字时代的网络安全新挑战?

随着数字时代的来临&#xff0c;我们迎来了无限的机遇&#xff0c;同时也伴随着网络安全领域新的挑战。网络攻击变得更加智能化和复杂化&#xff0c;威胁也在不断演化。为了应对这些新挑战&#xff0c;我们必须采取创新的网络安全策略和技术。本文将探讨数字时代网络安全的新挑…

Windows关闭zookeeper、rocketmq日志输出以及修改rocketmq的JVM内存占用大小

JDK-1.8zookeeper-3.4.14rocketmq-3.2.6 zookeeper 进入到zookeeper的conf目录 清空配置文件&#xff0c;只保留下面这一行。zookeeper关闭日志输出相对简单。 log4j.rootLoggerOFFrocketmq 进入到rocketmq的conf目录 logback_broker.xml <?xml version"1.0&q…

电脑c盘满了怎么清理?最新方法分享!(2023版)

“电脑c盘满了真的太恐怖了&#xff01;我平常没有什么清理的习惯&#xff0c;但是今天用电脑的时候&#xff0c;电脑变得异常卡顿。一看才发现是c盘满了&#xff01;但是我电脑中太多重要文件了&#xff0c;有什么比较简单又不容易误删文件的c盘清理方法推荐吗&#xff1f;” …

0门槛限制!快来领取你的专属元宇宙虚拟展厅!

数字化时代中&#xff0c;元宇宙虚拟展厅仿佛成为了一种新的潮流&#xff0c;虚拟展厅的出现为我们呈现出了一个超越现实的全新世界。元宇宙虚拟展厅以其多样性、互动性、沉浸式展示为特点&#xff0c;同产品进行交互&#xff0c;创造出逼真的虚拟环境&#xff0c;为广大用户打…

c++结构体调用类的构造函数

结构体初始化会调用构造函数 using namespace std;class cls{ public:cls(){c 5;cout << "cls init" << endl;};int c; };struct s{s(){cout << a << endl;cout << b.c << endl;};int a;cls b; };int main(){s s1; }结构体构造…

无涯教程-JavaScript - DB函数

描述 DB函数使用固定余额递减法返回指定期间内资产的折旧。 语法 DB (cost, salvage, life, period, [month])争论 Argument描述Required/OptionalCostThe initial cost of the asset.RequiredSalvageThe value at the end of the depreciation (sometimes called the salv…

docker搭建redis哨兵集群和分片集群

搭建哨兵集群 环境准备拉取镜像 搭建目标 &#xff1a; 一主而从三哨兵集群 docker pull redis:6.2.6 创建文件夹及配置文件 我这里在/usr/local/docker/redis目录下 在 redis-master、redis-slave1、redis-slave2 下分别建立data、 redis.conf、 sentinel.conf redis配置文件…

mysql5.8 免安装版(压缩包)win10 安装

目录 1、下载MySQL5.82、如何安装、配置my.ini配置注意 3初始化mysql3.1. 初始化mysql3.2. 安装mysql服务3.3. 启动mysql3.4. 登录mysql3.5. 修改root密码3.6. 配置远程连接 Mysql5.8安装踩坑记录&#xff0c;推荐使用Docker安装&#xff0c;我是电脑虚拟化可能会蓝屏没用这个功…

vue3+ts+uniapp小程序封装获取授权hook函数

vue3tsuniapp小程序封装获取授权hook函数 小程序授权的时候&#xff0c;如果点击拒绝授权&#xff0c;然后就再也不会出现授权了&#xff0c;除非用户手动去右上角…设置打开 通过uni官方api自己封装一个全局的提示: uni.getSetting :http://uniapp.dcloud.io/api/other/settin…

【Mysql系列】mysql中删除数据的几种方法

写在前面 在MySQL数据库中&#xff0c;删除数据是一个常见的操作&#xff0c;它允许从表中移除不再需要的数据。在执行删除操作时&#xff0c;需要谨慎&#xff0c;以免误删重要数据。 方法介绍 以下是MySQL中删除数据的几种方法&#xff1a; DELETE语句DROP TABLE语句TRUNCAT…

【紫光同创国产FPGA教程】——【PGL22G第八章】HDMI输出彩条实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光同创PGL22G开…

数据结构与算法基础-学习-34-基数排序(桶排序)

目录 一、基本思想 二、算法思路 1、个位排序 &#xff08;1&#xff09;分配 &#xff08;2&#xff09;收集 2、十分位排序 &#xff08;1&#xff09;分配 &#xff08;2&#xff09;收集 三、源码分享 1、InitMyBucket 2、DestroyMyBucket 3、ClearMyBucket 4、…

Linux内核分析与应用6-系统调用

本系列是对 陈莉君 老师 Linux 内核分析与应用[1] 的学习与记录。讲的非常之好&#xff0c;推荐观看 留此记录&#xff0c;蜻蜓点水,可作抛砖引玉 6.1 Linux中的各种API LSB (Linux Standards Base) POSIX: 可移植操作系统接口(Portable Operating System Interface of UNIX) L…

如何用手机号注册亚马逊买家账号

注册亚马逊买家号可以用手机号&#xff0c;也可以用邮箱进行注册。想要用手机号注册买家号&#xff0c;那么打开相应的官网后填写手机号、设置密码、接收短信验证即可。 而如果想要批量注册亚马逊买家号&#xff0c;可以使用亚马逊鲲鹏系统进行操作&#xff0c;亚马逊鲲鹏系统也…

playwright自动化上传附件

需求 自动设置上传头像 过程 1. 首先保存本地一个文件&#xff0c;例如 aaa.php file_path files/aaa.png 2. 获取输入类型为 "file" 的按钮 file_input_element page.locator(input[typefile]) 3. 将本地保存的图片路径赋值 file_input_element.set_input_…

算法训练营day49|动态规划 part10:(LeetCode 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II)

121. 买卖股票的最佳时机 题目链接&#x1f525; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大…

堆排序与TopK问题

一、堆排序 堆排序(升序)&#xff1a;堆排序的思想就是先用数组模拟建大堆&#xff0c;然后把根结点与最后一个结点值交换&#xff0c;最后一个结点的值就是最大值&#xff0c;然后再把前(n-1)个元素重新建大堆&#xff0c;然后根结点与最后一个结点值交换&#xff0c;就找出了…

javac不是内部或外部命令也不是可运行的程序如何解决?

小伙伴们你们有没有遇到过javac不是内部或外部命令,也不是可运行的程序这样的问题呢&#xff1f;大家遇到这样的问题不要慌&#xff0c;只要学会以下的操作你就可以轻松的解决了&#xff0c;具体的步骤操作就在下方&#xff0c;小伙伴们可以认真的看一看吧&#xff01; 1.首先…