C语言——结构体(struct)对齐

devtools/2024/9/24 1:15:14/

目录

前言

一、结构体对齐规则

1、结构体的总大小对齐规则

2、结构体成员的对齐规则

3、数组和结构体的对齐规则

二、改变编译器对齐数(#pragma pack)

三、如何减小结构体占用内存

1、 重新排列成员顺序

2、使用#pragma pack指令

3、使用位域

4、其他

总结



前言

        本文主要介绍C语言中,结构体的对齐规则、如何改变对齐数,对齐规则对内存的影响以及如何减小结构体占用的内存。


提示:小编系统为64位CentOS系统,默认编译器默认对齐数为8字节,如果结构体成员的大小大于编辑器的默认对齐数时,要以编辑器的对齐数为准,即二者取小。

一、结构体对齐规则

1、结构体的总大小对齐规则

        一个结构体的总共内存大小是其类型最大成员大小的整数倍如果结构体的最后一个成员之后没有足够的空间来满足对齐要求,编译器会在结构体末尾添加填充字节。

#include <stdio.h>struct A{int i; //4字节char c;  //1字节
};int main()
{printf("%d\n",sizeof(struct A));return 0;
}

 运行结果为:

8

        上面展示的例子中,结构体A中分别有两个成员,第一个成员为int型,占4个字节,第二成员为char型,占1个字节。根据结构体的总大小对齐规则,找出该结构体中类型最大的成员,即int型(4字节),所以结构体的总体大小必须为4字节的整数倍。将成员所占的字节数加起来为5个字节,但不是4的整数倍,所以要继续填充3个字节,一共8个字节,才能满足规则。

2、结构体成员的对齐规则

        结构体的每个成员相对于结构体开头的偏移量是该成员大小的整数倍。如果成员的大小小于对齐要求,编译器会在成员之间插入填充字节以满足对齐要求。

        偏移量:在计算机汇编语言中,偏移量是指存储单元的实际地址与其所在段的段地址之间的距离,也称为“段内偏移”或“有效地址”。它是程序的逻辑地址与段首地址的差值。

#include <stdio.h>  struct B{  char a; // 1 byte int b;  // 4 bytes  short c; // 2 bytes  
};  int main() 
{  printf("%d\n", sizeof(struct B)); return 0;  
}

 运行结果为:

12

        上面的例子中,结构体B一共有三个成员变量,其中int型(4字节)最大,根据规则一,最后结构体的总体大小应该为4的整数倍。其中第一变量a的偏移量为0,是其大小的整数倍;第二个变量b大小为4字节,即需要其偏移量为4的整数倍,所以b的偏移量为4,而a与b变量之间的空只能用字节填充,即填充三个字节。到此一共占了8个字节大小,第三个变量c大小为2个字节,同理,其偏移量需要为2的整数倍,由于前面刚好满足4字节对齐,所以偏移量为2的整数倍就自然满足了,即c的偏移量为8。到此一共占了10字节大小,但由于规则一,c变量后面还需要填充2个字节,一共12字节,满足4的整数倍。

3、数组和结构体的对齐规则

        数组中的每个元素都要满足上述对齐规则,即数组的每个元素都相对于数组开头的偏移量是其大小的整数倍。对于嵌套的结构体,嵌套的结构体本身也要满足其内部的对齐规则,并且嵌套结构体相对于外部结构体的偏移量也要满足对齐要求。

#include <stdio.h>  
struct C{  char a[3]; // 3 bytesint b;     // 4 bytes  
};  
struct D{  char i;        // 1 byte struct C c_t;  // 8 bytesint j;         // 4 bytes  
};  int main() 
{  printf("%d\n", sizeof(struct C));printf("%d\n", sizeof(struct D)); return 0;  
}

 运行结果为:

8
16

        上面例子中,结构体C中,第一个变量为char型数组,根据数组的特点,其三个元素偏移量必然对齐,且占三个字节。第二个变量为int型,4字节,偏移量为4的整数倍,即偏移量为4。满足规则一,规则二,所以结构体C大小为:8字节。

        在结构体D中,存在嵌套的结构体C,根据上面的规则一二,你很有可能算出来结构体D的大小为24,但其实它为16。为什么呢?因为在找最大类型变量是不能将结构体c_t当作一个整体,应该将其拆分来看,即最大类型的变量为4字节,总体大小需是4的整数倍。然后再看成员对其,第一个变量和第二个变量之间填充7字节,最终一共16字节。

二、改变编译器对齐数(#pragma pack)

        #pragma pack 指令:是用来修改编译器对结构体成员的对齐方式的。具体来说,它可以用来修改结构体或联合体中成员的偏移量对齐数,以及整个结构体或联合体的总大小对齐数。使用 #pragma pack(n) 时,n 指定了新的对齐字节数。结构体的成员将按照 n 字节对齐,同时整个结构体的总大小也将按照 n 字节对齐。

#include <stdio.h>  
struct C{  char a[3]; // 3 bytesint b;     // 4 bytes  
};  #pragma pack(1)
struct D{  char a[3]; // 3 bytesint b;     // 4 bytes  
};  #pragma pack(2)
struct E{  char a[3]; // 3 bytesint b;     // 4 bytes  
};  int main() 
{  printf("%d\n", sizeof(struct C));printf("%d\n", sizeof(struct D)); printf("%d\n", sizeof(struct E));return 0;  
}

  运行结果为:

8
7
8

          在上面的示例中,三个结构体的成员一摸一样,在编译结构体C时,还是原来的编译器对齐数,但在编译结构体D时,编译器对齐数改为了1,根据开头提示,这时候规则一二均取对齐数1,所以结构体E的大小变成了7。在编译结构体E时,对于总体大小对齐而言,在4和2之间选择2;对于成员偏移量对齐而言,第一个变量在1和2之间选择1,对于第二个变量在2和4之间选择2。最终算出结构体E的大小仍为8。

三、如何减小结构体占用内存

1、 重新排列成员顺序

        将占用空间小的成员放在前面,大的成员放在后面,并且尽量让相邻的成员类型相同,这样可以减少由于对齐而产生的填充字节。

        如上述规则二中的例子,将其重新排列后为:

#include <stdio.h>  struct B{  char a;  // 1 byte short c; // 2 bytes  int b;   // 4 bytes  };  int main() 
{  printf("%d\n", sizeof(struct B)); return 0;  
}

 运行结果为:

8
相比于原来的12,现在结构体的大小变为了8,减小了4字节的空间。

2、使用#pragma pack指令

        通过#pragma pack(n)指令可以修改编译器默认的对齐字节数。将n设置为一个较小的值可以减少由于对齐而产生的填充字节,从而减小结构体的总体大小。但是,这可能会影响程序的性能,因为非对齐的内存访问通常比对齐的内存访问要慢。 

#include <stdio.h>  
struct C{  char a; int b;   double c;  
};  #pragma pack(1)
struct D{  char a; int b;   double c;  
};  int main() 
{  printf("%d\n", sizeof(struct C));printf("%d\n", sizeof(struct D)); return 0;  
}

   运行结果为:

16
13
相同成员的结构体,D比C小了3个字节。

3、使用位域

        如果结构体中的某些成员只需要占用几个位,那么可以使用位域来定义这些成员。位域允许你将多个小成员打包到一个字节或更大的整数类型中,从而减少浪费的空间。

4、其他

        检查结构体定义,去除那些实际上并不需要的成员,可以直接减小结构体的总体大小。如果可能的话,尽量使用占用空间更小的数据类型。例如,如果某个成员的值范围较小,可以考虑使用charshort代替int


总结

        了解结构体的对齐规则对于提高内存使用效率、保证访问速度、避免硬件异常以及提高代码的可移植性都具有重要意义。在编写涉及结构体操作的代码时,我们应该了解和考虑这些对齐规则。

相关内容可以查看:C语言——结构体(Struct)详解+运用举例_struct在c语言中的用法-CSDN博客

有误之处望指正!!


http://www.ppmy.cn/devtools/85293.html

相关文章

LeetCode 637, 67, 399

文章目录 637. 二叉树的层平均值题目链接标签思路代码 67. 二进制求和题目链接标签思路代码 399. 除法求值题目链接标签思路导入value 属性find() 方法union() 方法query() 方法 代码 637. 二叉树的层平均值 题目链接 637. 二叉树的层平均值 标签 树 深度优先搜索 广度优先…

uni-app全局文件与常用API

文章目录 rpx响应式单位import导入css样式及scss变量用法与static目录import导入css样式uni.scss变量用法 pages.json页面路由globalStyle的属性pages设置页面路径及窗口表现tabBar设置底部菜单选项及iconfont图标 vite.config中安装插件unplugin-auto-import自动导入vue和unia…

photoshop学习笔记——移动工具

移动工具&#xff0c;可以对图层进行移动&#xff0c;快捷键 V 使用的素材已经放上了&#xff0c;直接下载即可 按住ctrl 可以自动选取&#xff0c;鼠标点击哪个对象&#xff0c;自动选中哪个图层 按住 shift 校正角度&#xff08;只能沿着直线移动&#xff09; 按住 alt 拖…

信创终端操作系统上vmware的命令行操作

原文链接&#xff1a;信创终端操作系统上vmware的命令行操作 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上使用命令行操作VMware的文章。通过命令行管理VMware虚拟机可以提高效率&#xff0c;特别是在需要批量操作或自动化管理时。本文将…

ctfshow-web入门-php特性(web147-web150_plus)

目录 1、web147 2、web148 3、web149 4、web150 5、web150_plus 1、web147 ^&#xff1a;匹配字符串的开头。 $&#xff1a;匹配字符串的结尾&#xff0c;确保整个字符串符合规则。 [a-z0-9_]&#xff1a;表示允许小写字母、数字和下划线。 *&#xff1a;匹配零个或多个前面…

GateWay网关微服务定位和理论知识

微服务架构的网关在哪里&#xff1f; 概念 SPring Cloud Gateway组件的核心是一系列的过滤器&#xff0c;通过这些过滤器可以将客户端发送的请求转发&#xff08;路由&#xff09;到对应的微服务。Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器&#xff0c;隐藏…

magento2 安装win环境和linux环境

win10 安装 安装前提&#xff0c;php,mysql,apach 或nginx 提前安装好 并且要php配置文件里&#xff0c;php.ini 把错误打开 display_errorsOn开始安装 检查环境 填写数据库信息 和ssl信息&#xff0c;如果ssl信息没有&#xff0c;则可以忽略 填写域名和后台地址&#xff0…

C语言100道基础拔高题(1)

1.有1,2,3,4这几个数字&#xff0c;问能组成多少个互不相同且无重复数字的三位数&#xff1f; 解题思路&#xff1a;首先输出由这几个数字所组成的所有三位数&#xff0c;接着再设置条件&#xff0c;使其输出的三位数不重复&#xff0c;下面我们来看下源代码。值得注意的是&…