•小试牛刀

  我们自定义两个结构体 A 和 B:

struct A
{
char c1;
char c2;
int i;
double d;
};
struct B
{
char c1;
int i;
char c2;
double d;
};

  通过定义我们可以看出,结构体 A 和 B 拥有相同的成员,只不过在排列顺序上有所不同;

  众所周知,char 类型占 1 个字节,int 类型占 4 个字节,double 类型占 8 个字节;

  那么,这两个结构体所占内存空间大小为多少呢?占用的空间是否相同?

  空口无凭,让我们通过编译器告诉我们答案(我使用的是 VS2022,X86)。

  在 main() 函数中输出如下语句:

int main()
{
printf("结构体A所占内存大小为:%d\n", sizeof(A));
printf("结构体B所占内存大小为:%d\n", sizeof(B)); return 0;
}

  运行之前,先盲猜一个结果:

sizeof(A) = sizeof(B) = sizeof(c1)+sizeof(c2)+sizeof(i)+sizeof(d) = 1+1+4+8 = 14

  到底对不对呢?让我们来看看运行结果:

  amazing~~

  竟然一个都没猜对,这究竟是怎么回事呢?

  下面开始进入今天的主题——struct 内存对齐。

•内存对齐

  一种提高内存访问速度的策略,CPU 在访问未对齐的内存可能需要经过两次的内存访问,而经过内存对齐一次就可以了。

  假定现在有一个 32 位处理器,那这个处理器一次性读取或写入都是四字节。

  假设现在有一个 32 位处理器要读取一个 int 类型的变量,在内存对齐的情况下,处理器是这样进行读取的:

  那如果数据存储没有按照内存对齐的方式进行的话,处理器就会这样进行读取:

  对比内存对齐和内存没有对齐两种情况我们可以明显地看到,在内存对齐的情况下,取得这个 int型 变量只需要经过一次寻址(0~3);

  但在内存没有对齐的情况下,取得这个 int型 变量需要经过两次的寻址(0~3 和 4~7),然后再合并数据。

  通过上述的分析,我们可以知道内存对齐能够提升性能,这也是我们要进行内存对齐的原因之一。

•内存对齐的原则

  1. 对于结构体的各个成员,除了第一个成员的偏移量为 0 外,其余成员的偏移量是 其实际长度 的整数倍,如果不是,则在前一个成员后面补充字节。
  2. 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍。
  3. 如程序中有  #pragma pack(n) 预编译指令,则所有成员对齐以 n字节 为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型。

  下面通过样例来分享一下我的见解,为方便理解,声明如下:

  • 定义的结构体包含 char , short , int , double类型各一个,并通过不同的组合构造出不同的结构体 Test01 , Test02 , Test03 , Test04
  • 内存地址的编号设置为 0~24
  • char 类型占1 个 字节,并用橙色填充
  • short 类型占 2个 字节,并用黄色填充
  • int 类型占 4个 字节,并用绿色填充
  • double 类型占 8个 字节,并用蓝色填充
  • 补充字节用黑色填充

Test01

struct Test01
{
char c;
short s;
int i;
double d;
}t1;

  内存分布情况:

  • 第一个成员 c 的偏移量为 0,所以成员 c 的内存空间的首地址为 0
  • 第二个成员 s 的内存空间的首地址为 2 号地址,偏移量为 2 - 0 = 2
  • 第三个成员 i 的内存空间的首地址为 4 号地址,偏移量为 4 - 0 = 4
  • 第三个成员 d 的内存空间的首地址为 8 号地址,偏移量为 8 - 0 = 8
  • Test01 所占内存大小为 16 个字节

  让我们通过输出来验证一下:

void showTest01()
{
printf("Test01所占内存大小:%d\n",sizeof(Test01));
//并按照声明顺序输出 Test01 中的成员变量地址对应的十六进制
printf("%p\n", &t1.c);
printf("%p\n", &t1.s);
printf("%p\n", &t1.i);
printf("%p\n", &t1.d);
}

  输出结果:

  我们将输出的十六进制地址转化为十进制:

00209400 -> 2135040

00209402 -> 2135042

00209404 -> 2135044

00209408 -> 2135048

  • 以第一个成员 c 的起始地址为起点
  • 第二个成员 s 的偏移量为 2
  • 第三个成员 i 的偏移量为 4
  • 第四个成员 d 的偏移量为 8
  • 所占内存大小为 16

  验证成功!

Test02

  调换一下成员顺序,再次测试:

struct Test02
{
char c;
double d;
int i;
short s;
}t2;

  内存分布情况:

  • 第一个成员 c 的偏移量为 0,所以成员 c 的内存空间的首地址为 0
  • 第二个成员 d 的内存空间的首地址为 8 号地址,偏移量为 8 - 0 = 8(double 类型的整倍数)
  • 第三个成员 i 的内存空间的首地址为 16 号地址,偏移量为 16 - 0 = 16(int 类型的整倍数)
  • 第三个成员 s 的内存空间的首地址为 20 号地址,偏移量为 20 - 0 = 20(short 类型的整倍数)
  • Test02 所占内存大小为 24 个字节(结构体占用内存大小是结构体内最大数据成员 double 的最小整数倍:24 / 8 = 4)

  接着通过输出来验证一下:

  我们将输出的十六进制地址转化为十进制:

007C9410 -> 8164368

007C9418 -> 8164376

007C9420 -> 8164384

007C9424 -> 8164388

  • 以第一个成员 c 的起始地址为起点
  • 第二个成员 d 的偏移量为  8164376 - 8164368 = 8
  • 第三个成员 i 的偏移量为  8164384 - 8164368 = 16
  • 第四个成员 d 的偏移量为  8164388 - 8164368 = 20
  • 所占内存大小为 24

  验证成功!

Test03 & Test04

struct Test03
{
short s;
double d;
char c;
int i;
}t3;
struct Test04
{
double d;
char c;
int i;
short s;
}t4;

  内存分布情况:

  可自行输出验证!!!

•总结

  通过自行模拟,再回过头看看内存对齐的原则,是不是有种恍然大明白的感觉~

  通过模拟上述不同情况,你会发现同种类型的成员变量通过不同的组合,所占用的总内存是不相同的;

  那么,关于结构体内成员定义的顺序应该遵循这样一个原则:按照长度递增的顺序依次定义各个成员。

•声明

参考资料

  1. struct 内存对齐 | CSDN

  2. struct结构体内存对齐解析 | CSDN

  3. 在线进制转换

C++ struct结构体内存对齐的更多相关文章

  1. C struct结构体内存对齐问题

    在空间看到别人的疑问引起了我的兴趣,刚好是我感兴趣的话题,就写一下.为了别人的疑问,也发表在qq空间里.因为下班比较晚,10点才到家,发表的也晚.其实是个简单的问题.  直接用实例和内存图说明: #i ...

  2. 【APUE】Chapter17 Advanced IPC & sign extension & 结构体内存对齐

    17.1 Introduction 这一章主要讲了UNIX Domain Sockets这样的进程间通讯方式,并列举了具体的几个例子. 17.2 UNIX Domain Sockets 这是一种特殊s ...

  3. 关于结构体内存对齐方式的总结(#pragma pack()和alignas())

    最近闲来无事,翻阅msdn,在预编译指令中,翻阅到#pragma pack这个预处理指令,这个预处理指令为结构体内存对齐指令,偶然发现还有另外的内存对齐指令aligns(C++11),__declsp ...

  4. struct结构体内存大小

    一. 基本原则 1. struct中成员变量的声明顺序,与成员变量对应的内存顺序是一致的: 2. struct本身的起始存储地址必须是成员变量中最长的数据类型的整倍数,注意是最长的数据类型,而不是最长 ...

  5. [C/C++] 结构体内存对齐用法

    一.为什么要内存对齐 经过内存对齐之后,CPU的内存访问速度大大提升; 内存空间按照byte划分,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内 ...

  6. [C/C++] 结构体内存对齐:alignas alignof pack

    简述: alignas(x):指定结构体内某个成员的对齐字节数,指定的对齐字节数不能小于它原本的字节数,且为2^n; #pragma pack(x):指定结构体的对齐方式,只能缩小结构体的对齐数,且为 ...

  7. C语言-结构体内存对齐

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

  8. c 结构体内存对齐详解

    0x00简介 首先要知道结构体的对齐规制 1.第一个成员在结构体变量偏移量为0的地址处 2.其他成员变量对齐到某个数字的整数倍的地址处 对齐数=编辑器默认的一个对齐数与该成员大小的较小值 vs中默认的 ...

  9. go语言结构体内存对齐

    cpu要想从内存读取数据,需要通过地址总线,把地址传输给内存,内存准备好数据,输出到数据总线,交给cpu,如果地址总线只有8根,那这个地址就只有8位可以表示[0,255]256个地址,因为表示不了更多 ...

随机推荐

  1. Linux 源码安装Ansible 参考篇

    Ansible 源码搭建配置 近期在学习自动化运维相关技术,文章主要模拟内网情况下对Ansible的安装演示,源码安装较为繁琐.枯燥,尤其是在实际安装过程中可能出现各式各样的问题,所有在安装过程中尽量 ...

  2. Typora软件的使用

    Typora软件 一.简介 1.该软件编写文档采用markdown格式是目前最为频繁的一种格式 2.该软件生成的文档后缀名是.md结尾 3.下载网址 https://www.typora.io/ 二. ...

  3. CentOS7防火墙firewall

    一.Firewall 1. 从CentOS7开始,默认使用firewall来配置防火墙,没有安装iptables(旧版默认安装). 2. firewall的配置文件是以xml的格式,存储在 /usr/ ...

  4. MYSQL时代是否将结束

    前言 已知MariaDB预计于今年下半年将以spac形式完成上市,最近也看了不少文章,发现MariaDB正在以一个迅猛的速度超越mysql,mysql 可以说是开源数据库中最具代表性的一个,甚至可以说 ...

  5. OpenHarmony移植:如何适配utils子系统之KV存储部件

    摘要:本文介绍移植开发板时如何适配utils子系统之KV存储部件,并介绍相关的运行机制原理. 本文分享自华为云社区<OpenHarmony移植案例与原理 - utils子系统之KV存储部件> ...

  6. 还不会使用linux?快来通过VMware安装centos系统吧~

    1.前言 Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统,其内核由林纳斯·本纳第克特·托瓦兹于1991年10月5日首次发布,它主要受到Minix和Unix思想的启发, ...

  7. 『无为则无心』Python面向对象 — 57、类属性和实例属性

    目录 1.类属性 (1)类属性的访问 (2)修改类属性 2.类属性和实例属性区别 1.类属性 (1)类属性的访问 类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有. 类属性可以使用 ...

  8. ctf.show-misc31

    (感谢阿姨)这个misc还是属于比较阴间的,并且学到了一个新的编码形式,直接开搞 下载附件得到压缩包,解压需要密码,可以看到没有输入密码解压也是得到了一个"file"文件,以txt ...

  9. 支持 dd 命令的简单的 GUI 实用程序

    Kindd-支持 dd 命令的简单的 GUI 实用程序 "Kindd",一个属于dd 命令的图形化前端.它是自由开源的.用 Qt Quick 所写的工具.总的来说,这个工具对那些对 ...

  10. 从零开始,开发一个 Web Office 套件(6):光标 & Click 事件

    <从零开始, 开发一个 Web Office 套件>系列博客目录 这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office ...