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

目录

前言

一、结构体对齐规则

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.mzph.cn/diannao/50884.shtml

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

相关文章

使用sheetjs导出CSV文本为excel

使用SheetJS&#xff08;也称为xlsx库&#xff09;导出CSV文本为Excel文件&#xff0c;你可以先将CSV文本解析为SheetJS支持的工作表格式&#xff0c;然后再将其写入为一个新的Excel文件。以下是一个简单的示例代码&#xff1a; const XLSX require(xlsx); const fs requir…

.net core 8.0 新建的项目无法使用 IApplicationBuilder

1、在项目文件中添加 <ItemGroup><FrameworkReference Include"Microsoft.AspNetCore.App" /> </ItemGroup> 2、在使用的地方添加 using Microsoft.AspNetCore.Builder;

工作流 Flowable

工作流包括业务流和审批流等业务流程。 在一个流程系统中&#xff0c;任务间往往存在复杂的依赖关系&#xff0c;为保证pipeline的正确执行&#xff0c;就是要解决各任务间依赖的问题&#xff0c;这样DAG结合拓扑排序是解决存在依赖关系的一类问题的利器。DAG ( Directed Acyc…

池化层pytorch最大池化练习

神经网络构建 class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 MaxPool2d(kernel_size3, ceil_modeFalse)def forward(self, input):output self.maxpool1(input)return output Tensorboard 处理 writer SummaryWriter("./l…

【React】详解如何获取 DOM 元素

文章目录 一、基础概念1. 什么是DOM&#xff1f;2. 为什么需要获取DOM&#xff1f; 二、使用 ref 获取DOM元素1. 基本概念2. 类组件中的 ref3. 函数组件中的 ref 三、 ref 的进阶用法1. 动态设置 ref2. ref 与函数组件的结合 四、处理特殊情况1. 多个 ref 的处理2. ref 与条件渲…

基于STM32F103的FreeRTOS系列(四)·FreeRTOS资料获取以及简介

目录 1. FreeRTOS简介 1.1 FreeRTOS介绍 1.2 为何选择FreeRTOS 1.3 FreeRTOS资料获取 1.3.1 官网下载 1.3.2 Github下载 1.3.3 托管网站下载 1.4 FreeRTOS的编程风格 1.4.1 数据类型 1.4.2 变量名 1.4.3 函数名 1.4.4 宏 1. FreeRTOS简介 1.1 Free…

11. Hibernate 持久化对象的各种状态

1. 前言 本节课和大家聊聊持久化对象的 3 种状态。通过本节课程&#xff0c;你将了解到&#xff1a; 持久化对象的 3 种状态&#xff1b;什么是对象持久化能力。 2. 持久化对象的状态 程序运行期间的数据都是存储在内存中。内存具有临时性。程序结束、计算机挂机…… 内存中…

前端开发大屏适配几种方案

方案一&#xff1a;vw&#xff08;单位&#xff09; 假设设计稿尺寸为 1920*1080&#xff0c;直接使用 vw 单位&#xff0c;屏幕的宽默认为 100vw&#xff0c;那么100vw 1920px&#xff0c; 1vw 19.2px 。 新建px2vw.scss / 使用 scss 的 math 函数 use "sass:math&q…

Web前端浅谈ArkTS组件开发

本文由JS老狗原创。 有幸参与本厂APP的鸿蒙化改造&#xff0c;学习了ArkTS以及IDE的相关知识&#xff0c;并有机会在ISSUE上与鸿蒙各路大佬交流&#xff0c;获益颇丰。 本篇文章将从一个Web前端的视角出发&#xff0c;浅谈ArkTS组件开发的基础问题&#xff0c;比如属性传递、插…

hamcrest 断言框架使用示例和优势分析

引言 在软件测试领域&#xff0c;断言是验证代码行为是否符合预期的关键环节。Hamcrest 断言框架&#xff0c;以其独特的匹配器&#xff08;Matcher&#xff09;概念和清晰的失败信息&#xff0c;赢得了广泛的赞誉。尽管 Python 标准库中没有内置的 Hamcrest 库&#xff0c;但…

【Linux】-----工具篇(编译器gcc/g++,调试器gdb)

目录 一、gcc/g 简单认识 程序的翻译过程认识gcc 预处理(宏替换) 编译 汇编 链接 宏观认识 如何理解&#xff08;核心&#xff09; 什么是链接&#xff1f; 链接的分类 二、gdb 基本的认识 基本操作及指令 安装gdb 启动gdb ​编辑 显示源代码(list) 运行程序…

RDF蕴涵插值的详细解释

在逻辑和数学中,插值定理(Interpolation Theorem)是关于公式间蕴涵关系的一种性质。对于RDF蕴涵,插值的含义涉及在两个RDF图之间找到一个中间图,这个中间图与这两个图在语义上有某种特定的关系。 插值定理概述 在一阶逻辑中,插值定理通常表示如下: 如果一个公式 ( A …

SQL labs-SQL注入(三,sqlmap使用)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 引言&#xff1a; 盲注简述&#xff1a;是在没有回显得情况下采用的注入方式&#xff0c;分为布尔盲注和时间盲注。 布尔盲注&#xff1a;布尔仅有两种形式&#xff0c;ture&#…

带分页的el-table获取全选的勾选状态

有时候要自定义el-table的全选按钮的事件操作 用它本身全选点击事件&#xff0c;然后根据点击状态TRUE/FALSE来做自己的操作 select-all"handleSelectAll" handleSelectAll(){ // refpushMultipleTable 的el-table的全选点击TRUE/FALSE if(this.$refs[pushM…

学习笔记:MySQL数据库操作3

1. 创建数据库和表 创建数据库 mydb11_stu 并使用该数据库。创建 student 表&#xff0c;包含字段&#xff1a;学号&#xff08;主键&#xff0c;唯一&#xff09;&#xff0c;姓名&#xff0c;性别&#xff0c;出生年份&#xff0c;系别&#xff0c;地址。创建 score 表&…

UE5 UE4 使用python进行编辑器操作

使用UE 4.25以上版本后&#xff0c;python代码改动相对较少。 如下类库在4.20/21/22等早起版本不适用&#xff0c;建议查询UE的python文档 unreal.EditorAssetLibrary 1.获取当前选中的资源&#xff08;Content中&#xff09; # 获取当前选中的资产selected_assets unreal.E…

C#、Net6、WebApi报表方案

目录 1 Pdf表单方案 1.1出现如下错误提示: 1.2 字体路径使用 2 Docx报表模板方案 2.1 pdf方案缺陷 2.2 解决方案 3 Spire.Doc报表方案 3.1 Docx方案缺陷 3.2 解决方案 4 插入复选框 5 WebApi文件流下载接口 6 软件获取方式 1 Pdf表单方案 使用【Adobe Acrobat P…

Avalonia中的附加属性

文章目录 附加属性的基本概念定义附加属性:使用附加属性:附加属性的创建定义附加属性类:实现附加属性的访问器:示例代码使用附加属性附加属性的应用场景布局和位置:数据绑定:事件处理:样式和主题:附加属性的优缺点优点:缺点:总结在Avalonia中,附加属性(Attached Pr…

python—pandas基础(2)

文章目录 列操作修改变量列筛选变量列使用.loc[]&#xff08;基于标签)使用.iloc[]&#xff08;基于整数位置&#xff09;使用.filter()方法 删除变量列添加变量列 变量类型的转换Pandas 支持的数据类型在不同数据类型间转换 建立索引新建数据框时建立索引读入数据时建立索引指…

Vue---vue3+vite项目内使用devtools,快速开发!

背景 我们在前期开发时&#xff0c;一般使用chrome或者edge浏览器&#xff0c;会使用vue-devtools或react-devtools&#xff08;此插件个人未使用&#xff0c;可百度下是否可内嵌入项目&#xff01;&#xff09;来审查vue项目&#xff1b;这个需要安转浏览器插件才可支持&…