【嵌入式编程】-C语言结构体成员对齐、填充和数据打包

news2024/11/13 10:08:27

C语言结构体成员对齐、填充和数据打包

文章目录

  • C语言结构体成员对齐、填充和数据打包
    • 1、内存中的数据对齐
    • 2、C语言中的结构填充
    • 3、如何减少结构填充
    • 4、C语言中结构填充的常见问题解答

在 C 语言中,结构用作数据包。它们不提供任何数据封装或数据隐藏功能。在本文中,我们将讨论 C 语言中结构体填充的属性以及数据对齐和结构打包。

1、内存中的数据对齐

C 中的每种数据类型都有对齐要求(实际上,它是由处理器架构而不是语言强制要求的)。处理器的处理字长与数据总线大小的字长相同。在 32 位计算机上,处理字大小为 4 个字节。

在这里插入图片描述

从历史上看,内存是字节可寻址的,并按顺序排列。如果内存被安排为一个字节宽度的单个组,则处理器需要发出 4 个内存读取周期来获取整数。在一个内存周期内读取整数的所有 4 个字节更经济。为了利用这种优势,存储器将排列为一组 4 个组,如上图所示。

内存寻址仍是顺序的。如果Bank0 占用地址 X,则Bank1、Bank2 和Bank3 将位于 (X + 1)、(X + 2) 和 (X + 3) 地址。如果在 X 地址上分配了 4 个字节的整数(X 是 4 的倍数),则处理器只需要一个内存周期即可读取整个整数。然而,如果整数分配在 4 的倍数以外的地址,则它跨越两行Bank,如下图所示。这样的整数需要两个内存读取周期来获取数据。

在这里插入图片描述

一个变量的数据对齐方式涉及到数据在这些存储区中的存储方式。例如,在 32 位计算机上,int 的自然对齐方式为 4 个字节。当数据类型自然对齐时,CPU 会在最短的读取周期内获取它。

同样,短 int 的自然对齐方式为 2 个字节。这意味着短 int 可以存储在 bank 0 – bank 1 对或 bank 2 – bank 3 对中。双精度需要 8 个字节,并在内存库中占据两行。任何 double 的未对齐都将强制两个以上的读取周期来获取 double 数据。

请注意,在 32 位计算机上,将在 8 字节边界上分配双精度变量,并且需要两个内存读取周期。在 64 位机器上,基于多个Bank,将在 8 字节边界上分配一个双精度变量,并且只需要一个内存读取周期。

2、C语言中的结构填充

结构填充是在结构中添加一些空字节的内存,以自然地对齐内存中的数据成员。这样做是为了最小化 CPU 读取周期,以检索结构中的不同数据成员。

尝试计算以下结构的大小:

// structure A 
typedef struct structa_tag { 
	char c; 
	short int s; 
} structa_t; 

// structure B 
typedef struct structb_tag { 
	short int s; 
	char c; 
	int i; 
} structb_t; 

// structure C 
typedef struct structc_tag { 
	char c; 
	double d; 
	int s; 
} structc_t; 

// structure D 
typedef struct structd_tag { 
	double d; 
	int s; 
	char c; 
} structd_t;

通过直接将所有成员的大小相加来计算每个结构的大小,我们得到:

  • struct A 的大小 = (char + short int) 的大小 = 1 + 2 = 3。
  • struct B 的大小 = (short int + char + int) = 2 + 1 + 4 = 7 。
  • struct C 的大小 = (char + double + int) 的大小 = 1 + 8 + 4 = 13。
  • struct D 的大小 = (double + int + char) 的大小 = 8 + 4 + 1= 13。
// C Program to demonstrate the structure padding property 
#include <stdio.h> 

// Alignment requirements 
// (typical 32 bit machine) 

// char     1 byte 
// short int  2 bytes 
// int     4 bytes 
// double    8 bytes 

// structure A 
typedef struct structa_tag { 
	char c; 
	short int s; 
} structa_t; 

// structure B 
typedef struct structb_tag { 
	short int s; 
	char c; 
	int i; 
} structb_t; 

// structure C 
typedef struct structc_tag { 
	char c; 
	double d; 
	int s; 
} structc_t; 

// structure D 
typedef struct structd_tag { 
	double d; 
	int s; 
	char c; 
} structd_t; 

int main() 
{ 
	printf("sizeof(structa_t) = %lu\n", sizeof(structa_t)); 
	printf("sizeof(structb_t) = %lu\n", sizeof(structb_t)); 
	printf("sizeof(structc_t) = %lu\n", sizeof(structc_t)); 
	printf("sizeof(structd_t) = %lu\n", sizeof(structd_t)); 

	return 0; 
}

正如我们所看到的,结构的大小与我们计算的大小不同。

这是因为各种数据类型的对齐要求,结构的每个成员都应该自然对齐。结构的成员按升序顺序分配。

让我们分析一下上面程序中声明的每个结构。为了方便起见,假设每个结构类型变量都分配在一个 4 字节的边界上(比如 0x0000),即结构的基址是 4 的倍数(不一定总是这样,请参阅structc_t的解释)。

structure A
structa_t第一个元素是 char,它对齐一个字节,然后是 short int。short int 是 2 个字节对齐。如果短 int 元素在 char 元素之后立即分配,它将从一个奇数地址边界开始。编译器将在 char 后插入一个填充字节,以确保短 int 的地址倍数为 2(即 2 个字节对齐)。structa_t的总大小将是:

sizeof(char) + 1 (padding) + sizeof(short), 1 + 1 + 2 = 4字节

structure B

structb_t 的第一个成员是短 int,后跟 char。由于 char 可以在任何字节边界上,因此短 int 和 char 之间不需要填充,因此它们总共占用 3 个字节。下一个成员是 int。如果立即分配 int,它将从奇数字节边界开始。我们需要在 char 成员之后填充 1 字节,以使下一个 int 成员的地址 4 字节对齐。总的来说,

2 + 1 + 1 (padding) + 4 = 8字节

structure C

每个结构也将有对齐要求。

应用相同的分析,structc_t需要 sizeof(char) + 7 字节填充 + sizeof(double) + sizeof(int) = 1 + 7 + 8 + 4 = 20 字节。但是,sizeof(structc_t) 为 24 个字节。这是因为,除了结构成员外,结构类型变量也将具有自然对齐。让我们通过一个例子来理解它。比如说,我们声明了一个structc_t数组,如下所示

structc_t structc_array[3];

假设structc_array的基址是0x0000的,以便于计算。如果structc_t占用我们计算的 20 (0x14) 字节,则第二个structc_t数组元素(索引为 1)将在 0x0000 + 0x0014 = 0x0014。它是数组的索引 1 元素的起始地址。此structc_t的 double 成员将在 0x0014 + 0x1 + 0x7 = 0x001C(十进制 28)上分配,它不是 8 的倍数,并且与 double 的对齐要求冲突。正如我们在顶部提到的,double 的对齐要求是 8 个字节。

为了避免这种错位,编译器将对齐要求引入到每个结构中。它将作为结构中最大的成员。在我们的例子中,structa_t 的对齐是 2,structb_t 是 4,structc_t 是 8。如果我们需要嵌套结构,则最大内部结构的大小将是直接较大结构的对齐方式。

在上述程序structc_t,int 成员后面将有一个 4 字节的填充,以使结构大小是其对齐方式的倍数。因此,(structc_t) 的大小为 24 字节。即使在阵列中,它也能保证正确的对齐。

structure D

以类似的方式,结构 D 的大小为:

sizeof(double) + sizeof(int) + sizeof(char) + padding(3) = 8 + 4 + 1 + 3 = 16字节

3、如何减少结构填充

到现在为止,我们知道填充可能是不可避免的。有一种方法可以最小化填充。程序员应按大小递增/递减的顺序声明结构成员。我们的代码中structd_t给出了一个示例,其大小为 16 个字节,而不是 24 个字节的structc_t。

什么是结构体对齐?

有时,必须避免在结构成员之间填充字节。例如,读取 ELF 文件头或 BMP 或 JPEG 文件头的内容。我们需要定义一个类似于标题布局的结构并映射它。但是,在接触这些成员时应谨慎行事。通常,逐字节读取是避免未对齐异常的一种选择,但会对性能造成影响。

大多数编译器都提供非标准扩展来关闭默认填充,如编译指示或命令行开关。有关更多详细信息,请参阅相应编译器的文档。

在 GCC 中,我们可以使用以下代码进行结构包装:

#pragma pack(1)

或者

struct name {
    ...
}__attribute__((packed));

如下面代码所示:

// C Program to demonstrate the structure packing 
#include <stdio.h> 
#pragma pack(1) 

// structure A 
typedef struct structa_tag { 
	char c; 
	short int s; 
} structa_t; 

// structure B 
typedef struct structb_tag { 
	short int s; 
	char c; 
	int i; 
} structb_t; 

// structure C 
typedef struct structc_tag { 
	char c; 
	double d; 
	int s; 
} structc_t; 

// structure D 
typedef struct structd_tag { 
	double d; 
	int s; 
	char c; 
} structd_t; 

int main() 
{ 
	printf("sizeof(structa_t) = %lu\n", sizeof(structa_t)); 
	printf("sizeof(structb_t) = %lu\n", sizeof(structb_t)); 
	printf("sizeof(structc_t) = %lu\n", sizeof(structc_t)); 
	printf("sizeof(structd_t) = %lu\n", sizeof(structd_t)); 

	return 0; 
}

sizeof(structa_t) = 3
sizeof(structb_t) = 7
sizeof(structc_t) = 13
sizeof(structd_t) = 13

4、C语言中结构填充的常见问题解答

1)堆栈是否应用对齐?

是的。堆栈也是内存。系统程序员应使用正确对齐的内存地址加载堆栈指针。通常,处理器不会检查堆栈对齐,程序员有责任确保堆栈内存的正确对齐。任何错位都会导致运行时意外。
例如,如果处理器字长度为 32 位,则堆栈指针也应对齐为 4 个字节的倍数。

2)如果 char 数据放置在 bank 0 以外的 bank 中,则在读取内存时,它将被放置在错误的数据行上。处理器如何处理字符类型?

通常,处理器会根据指令识别数据类型(例如,ARM 处理器上的 LDRB)。根据存储的字节库,处理器将字节移位到最低有效数据线上。

3)当参数在堆栈上传递时,它们是否受到对齐的影响?

是的。编译器帮助程序员进行正确的对齐。例如,如果将 16 位值推送到 32 位宽的堆栈上,则该值会自动填充到 32 位的零。请考虑以下程序。

void argument_alignment_check( char c1, char c2 ) 
{ 
   // Considering downward stack 
   // (on upward stack the output will be negative) 
   printf("Displacement %d\n", (int)&c2 - (int)&c1); 
} 

在 32 位计算机上,输出将为 4。这是因为由于对齐要求,每个字符占用 4 个字节。

4)如果我们尝试访问未对齐的数据,会发生什么?

这取决于处理器体系结构。如果访问未对齐,处理器会自动发出足够的内存读取周期,并将数据正确打包到数据总线上。惩罚是针对绩效的。而很少有处理器没有最后两条地址线,这意味着无法访问奇数字节边界。每个数据访问都必须正确对齐(4 个字节)。未对准访问是此类处理器的一个关键例外。如果忽略异常,则读取的数据将不正确,因此结果将不正确。

5)有没有办法查询数据类型的对齐要求?

是的。编译器为此类需求提供非标准扩展。例如,Visual Studio 中的 __alignof() 有助于获取数据类型的对齐要求。有关详细信息,请阅读 MSDN。

6)当内存读取在 32 位计算机上一次读取 4 个字节时,为什么要在 8 字节边界上对齐双精度类型?

需要注意的是,大多数处理器都有一个数学协处理器,称为浮点单元 (FPU)。代码中的任何浮点运算都将转换为 FPU 指令。主处理器与浮点执行无关。所有这些都将在幕后完成。

按照标准,double 类型将占用 8 个字节。而且,在 FPU 中执行的每个浮点运算都将是 64 位长度。甚至浮点类型也会在执行之前提升到 64 位。

FPU 寄存器的 64 位长度强制在 8 字节边界上分配 double 类型。假设在 FPU 操作的情况下,数据获取可能会有所不同,意思是数据总线,因为它进入 FPU。因此,对于双精度类型(预计位于 8 字节边界上),地址解码将有所不同。这意味着浮点单元的地址解码电路将没有最后 3 个引脚。

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

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

相关文章

HarmonyOS【ArkUI组件--TextInput】

1.文本输入框基本用法 2. 使用文本输入框组件&#xff08;如何实现输入数字改变图片大小&#xff09; 在此博客的基础上继续编写&#xff1a;HarmonyOS【ArkUI组件--Text】-CSDN博客 ①代码如下&#xff1a; import font from ohos.font Entry Component struct Index {State …

【吊打面试官系列-Mysql面试题】Myql 中的事务回滚机制概述 ?

大家好&#xff0c;我是锋哥。今天分享关于 【Myql 中的事务回滚机制概述 ?】面试题&#xff0c;希望对大家有帮助&#xff1b; Myql 中的事务回滚机制概述 ? 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个不可分割的工作单位…

RK3568技术笔记十二 Android编译方法

Android源码说明 Android源码在SAIL-RK3568开发板光盘->Android->源代码中&#xff0c;由于android源码太大&#xff0c;在进行压缩时&#xff0c;进行分包压缩&#xff0c;因此有4部分&#xff0c;如图所示&#xff1a; 进行解压时&#xff0c;需将4部分压缩包放置同一…

linux写代码环境和工具

基础指令 目录 前言 二、yum工具的使用 1.yum是什么&#xff1f; 2.查看软件包 3.配置sudo 4.如何卸载软件 三、vim的使用 1. vim的基本概念 2. vim的基本操作 3. vim正常模式命令集 4.简单vim配置 四、Linux编译器-gcc/g使用 1、格式 2、gcc选项 3.gcc/g工作和…

四连杆机构运动学仿真 | Matlab源码+理论文本【超详细】

【程序简介】&#x1f4bb;&#x1f50d; 本程序通过matlab实现了四连杆机构的运动学仿真编程&#xff0c;动态展现了四连杆机构的角位移、角速度和角加速度的时程曲线&#xff0c;除了程序本身&#xff0c;还提供了机构运动学详细的公式推导文档&#xff0c;从而帮助大家更好…

录屏软件OBS简单使用

录屏软件OBS简单使用 官网下载地址&#xff1a; https://obsproject.com/ window解压直接使用版&#xff1a; 链接: https://pan.baidu.com/s/1495KDkvuDnjqdOvm1IG4Fw 提取码: 9xcr 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 简单使用 解压 解压window解…

Sui主网升级至V1.27.2版本

其他升级要点如下所示&#xff1a; 重点&#xff1a; #17245 增加了一个新的协议版本&#xff0c;并在开发网络上启用了Move枚举。 JSON-RPC #17245: 在返回的JSON-RPC结果中增加了对Move枚举值的支持。 GraphQL #17245: 增加了对Move枚举值和类型的支持。 CLI #179…

SpringBoot配置第三方专业缓存技术Ehcache

Ehcache缓存技术 我们刚才是用Springboot提供的默认缓存技术 我们用的是simple 是一个内存级的缓存 我们接下来要使用专业的缓存技术了 Ehcache 是一个流行的开源 Java 分布式缓存&#xff0c;由 Terracotta 公司开发和维护。它提供了一个快速、可扩展、易于集成的内存缓存…

CI /CD学习

CI/CD概述 CI/CD 是持续集成和持续交付/部署的缩写&#xff0c;旨在简化并加快软件开发生命周期。 持续集成&#xff08;CI&#xff09;是指自动且频繁地将代码更改集成到共享源代码存储库中的做法。持续交付和/或持续部署&#xff08;CD&#xff09;是一个由两部分组成的过程…

山东大学软件学院创新项目实训开发日志——收尾篇

山东大学软件学院创新项目实训开发日志——收尾篇 项目名称&#xff1a;ModuFusion Visionary&#xff1a;实现跨模态文本与视觉的相关推荐 -------项目目标&#xff1a; 本项目旨在开发一款跨模态交互式应用&#xff0c;用户可以上传图片或视频&#xff0c;并使用文本、点、…

flutter 打包 exe

采用官方的MSIX打包 原文链接 https://blog.csdn.net/weixin_44786530/article/details/135308360 第一步&#xff1a;安装依赖 在项目根目录&#xff0c;执行命令&#xff1a; flutter pub add --dev msix 等待安装完成&#xff0c;就好了 第二步&#xff1a;打包编译 当m…

嵌入式技术学习——Linux环境编程(高级编程)——shell编程

一、shell编程的基础介绍 1.为什么要进行shell编程? 在Linux系统中&#xff0c;虽然有各种各样的图形化接口工具&#xff0c;但是shell仍然是一个非常灵活的 工具。 Shell不仅仅是命令的收集&#xff0c;而且是一门非常棒的编程语言。 您可以通过使用shell使大量的任务自动化…

群辉NAS与电脑间的文件自动同步

目录 一、套件简介 二、套件的安装 三、电脑端程序 (1)启用团队文件夹 (2)设置同步任务 (3)测试同步 四、手机同步 (1)安装app (2)测试 (3)同步任务 (3)其它同步任务 最早使用的网盘叫“快盘”,不知道朋友们有用过的没有。当时最常用的功能就是电脑与…

相交链表(Leetcode)

题目分析&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 相交链表&#xff1a;首先我想到的第一个思路是&#xff1a;如图可知&#xff0c;A和B链表存在长度差&#xff0c;从左边一起遍历链表不好找交点&#xff0c;那我们就从后面开始找&#xff0c;但是这是单链表&…

期望23K,go高级社招面试复盘

面经哥只做互联网社招面试经历分享&#xff0c;关注我&#xff0c;每日推送精选面经&#xff0c;面试前&#xff0c;先找面经哥 我最终还是上岸了&#xff0c;花了一周总结了3万字的go社招高级面试知识体系思维导图&#xff0c;分享出来希望能帮助有缘人吧&#xff0c;以下只是…

AI金融投资:批量下载巨潮资讯基金招募说明书

打开巨潮资讯的基金招募说明书页面&#xff1a; http://www.cninfo.com.cn/new/fulltextSearch/full?searchkey%E5%B0%81%E9%97%AD%E5%BC%8F%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E8%AF%81%E5%88%B8%E6%8A%95%E8%B5%84%E5%9F%BA%E9%87%91%E6%8B%9B%E5%8B%9F%E8%AF%B4%E6%98%…

如何配置node.js环境

文章目录 step1. 下载node.js安装包step2. 创建node_global, node_cache文件夹step3.配置node环境变量step3. cmd窗口检查安装的node和npm版本号step4. 设置缓存路径\全局安装路径\下载镜像step5. 测试配置的nodejs环境 step1. 下载node.js安装包 下载地址&#xff1a;node.js…

Windows下MySQL数据库定期备份SQL文件与删除历史备份文件.bat脚本

目录 一、功能需求 二、解决方案 (1)新建文件夹及批处理文件 (2)编写备份脚本 ①完整脚本 ②参数修改 (3)编写定期删除备份脚本 ①根据文件名识别日期进行删除 ② 根据文件的修改日期删除 (4)设置定时器 (5)常见报错与处理 一、功能需求 在Windows系统下…

【吊打面试官系列-Mysql面试题】SQL 语言包括哪几部分?每部分都有哪些操作关键字?

大家好&#xff0c;我是锋哥。今天分享关于 【SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f; SQL 语言包括数据定义(DDL)、…

Linux PXE高效批量装机

部署PXE远程安装服务 在大规模的 Linux 应用环境中&#xff0c;如 Web 群集、分布式计算等&#xff0c;服务器往往并不配备光驱设备&#xff0c;在这种情况下&#xff0c;如何为数十乃至上百台服务器裸机快速安装系统呢?传统的USB光驱、移动硬盘等安装方法显然已经难以满足需…