C语言结构体字节对齐(内存对齐)之#pragma pack和__attribute__((packed)的使用

在不使用#pragma pack__attribute__((packed) 等选项来自定义字节对齐大小的情况下,关于正常字节对齐的描述,可参考博文:

C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间_SOC罗三炮的博客-CSDN博客同学可以尝试将char f 注释,最后将得到24,也可以从侧面说明再加上一个char f,其大小肯定大于等于24 byte。Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照。sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3。sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3。s中char ch1;占用 1Byte(存储位置8)的,sizeof的结果是40。最大元素大小的整数倍地址。_联合体占用的内存空间https://blog.csdn.net/luolaihua2018/article/details/115372273?ops_request_misc=&request_id=fd0dff42f0ee4596a85be00069ed9893&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-2-115372273-null-null.268%5Ev1%5Econtrol&utm_term=%E5%AD%97%E8%8A%82%E5%AF%B9%E9%BD%90&spm=1018.2226.3001.4450本文主要讨论如何使用#pragma pack和__attribute__((packed)等选项来自定义字节对齐大小。

目录

1,对整个结构体进行打包压缩

 #pragma pack(n) 实验

__attribute__((packed)) 实验

 2,编译器内存对齐操作

test1,正常情况下的对齐操作,12 bytes

 test 2,使用__attribute__((packed)),相当于#pragma pack(1),8 bytes (最小)

 test3, 使用#pragma pack(2),10 bytes

  test4, 使用#pragma pack(4),12 bytes

  test5, 使用#pragma pack(5) ,12bytes

 test6, 使用#pragma pack(8) ,12bytes 

test7,使用__attribute__((packed))对结构体中的成员进行修饰,10 bytes

3,使用armlink 选项 --info=sizes 查看 结构体大小


使用pack功能将数据结构进行打包,以减小应用程序对内存的占用,这一点在嵌入式系统中,尤其是需要存储和访问大量内存的时候,显得尤其重要。

如果没有使用#pragma pack和__attribute__((packed)等选项,将数据结构进行打包。编译器为了提高对数据成员的访问速度,通常会在不同大小的数据成员之间插入空白内存(padding),进行补齐操作,即内存对齐。默认情况下的内存对齐,需要遵守一下三条规则:

  1. 数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
  2. 结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
  3. 结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐
     

ARM的嵌入式编译器提供了编译选项和属性(#pragma pack和__attribute__((packed)),可以让程序员自定义内存对齐的大小,对数据结构(结构体或联合体)进行打包,必要情况下甚至可以不需要内存留白,即不对齐。

#pragma pack (<n>)对于结构体中的每个成员,如果 <n> 小于该成员的默认对齐大小(关于默认对齐大小,可参考上文中提到的博文),则使用 <n> bytes 作为该成员的对齐大小。如果<n> 大于默认对齐大小,则使用默认对齐大小。简言之,使用<n> 和默认对齐大小中较小的那个,作为该数据成员的对齐大小。 详情见: __alignof__.
__attribute__((packed))等效于#pragma pack(1),最小对齐大小,即不进行内存对齐,可以对结构体中的单个数据成员使用。

1,对整个结构体进行打包压缩

在声明结构体或联合体时,可以使用__attribute__((packed)) 或 #pragma pack(n) 对整个结构体进行声明。这样,结构体或联合体里面的每个数据成员都会按照新的对齐大小进行对齐。

__attribute__((packed)) 和  #pragma pack(n)的作用范围不同,使用__attribute__((packed))可以精准地对其声明的结构体或者联合体进行打包压缩,而不会影响其他及结构体,甚至是其子结构体(其数据成员也为结构体)的正常内存对齐。而 #pragma pack(n) 的作用范围更大,在使用#pragma pack(n)之后的所有结构体联合体(不包括子结构体)的内存对齐都会受到其影响。

struct __attribute__((packed)) stc
{char one;short two;char three;int four;
} c,d;#pragma pack (1)
struct stc
{char one;short two;char three;int four;
} c,d;

 #pragma pack(n) 实验

/* ================= test 1 ==================*/
struct stc
{char one;short two;char three;int four;
} cc; #pragma pack (1)
struct stcd
{char one;short two;char three;int four;
} dd;sizeof(cc) = 12
sizeof(dd) = 8/* ================= test 2 ==================*/
#pragma pack (1)
struct stc
{char one;short two;char three;int four;
} cc; struct stcd
{char one;short two;char three;int four;
} dd;sizeof(cc) = 8
sizeof(dd) = 8/* ================= test3 ==================*/typedef struct 
{char one;short two;char three;int four;
} stc;
stc cc;struct stcd
{char one;short two;char three;int four;stc C;
} dd;sizeof(cc) = 12
sizeof(dd) = 24/* ================= test4 ==================*/#pragma pack (1)
typedef struct 
{char one;short two;char three;int four;
} stc;stc cc;
struct stcd
{char one;short two;char three;int four;stc C;
} dd;sizeof(cc) = 8
sizeof(dd) = 16/* ================= test5 ==================*/typedef struct 
{char one;short two;char three;int four;
} stc;
stc cc;
#pragma pack (1)
struct stcd
{char one;short two;char three;int four;
//    #pragma pack (1)stc C;
} dd;sizeof(cc) = 12
sizeof(dd) = 20/* ================= test6 ==================*/typedef struct 
{char one;short two;char three;int four;
} stc;
stc cc;struct stcd
{char one;short two;char three;int four;#pragma pack (1)stc C;
} dd;sizeof(cc) = 12
sizeof(dd) = 20

从test4,test5和test6可以看到,虽然 在#pragma pack(n)之后,都会受到其对齐影响,但是结构体dd中的C结构体仍保持自然的内存对齐,并未受影响。此外,在结构体dd内使用#pragma pack(n),仍相当于对整个结构体dd起作用。

__attribute__((packed)) 实验

/* ================= test1 ==================*/typedef struct __attribute__((packed))
{char one;short two;char three;int four;
} stc;stc cc;struct stcd
{char one;short two;char three;int four;stc C;
} dd;sizeof(cc) = 8
sizeof(dd) = 20/* ================= test2 ==================*/
typedef struct __attribute__((packed))
{char one;short two;char three;int four;
} stc;stc cc;struct __attribute__((packed)) stcd
{char one;short two;char three;int four;stc C;
} dd;sizeof(cc) = 8
sizeof(dd) = 16/* ================= test3 ==================*/
typedef struct 
{char one;short two;char three;int four;
} stc;stc cc;struct __attribute__((packed)) stcd
{char one;short two;char three;int four;stc C;
} dd;sizeof(cc) = 12
sizeof(dd) = 20

使用  __attribute__((packed))可以精准地对想要打包压缩的结构体进行操作,而不像#pragma pack(n)的全局生效。同时也是对整个结构体生效,如果结构体内的数据成员仍为结构体,则不对子结构体生效。 __attribute__((packed))相当于#pragma pack(1),即尽可能地压缩结构体空间,不像#pragma pack(n)那样可以选择不同大小的对齐尺寸。

使用__attribute__((packed))声明变量时,要注意其摆放位置,比如test4的声明是错误的,虽然可以编译通过,但是__attribute__((packed))并不会生效,还会报一个warning:

[Warning] 'packed' attribute ignored [-Wattributes]

test5中的声明方式是正确的: 

/* ================ test 4 ==============*/
typedef struct
{char one;short two;char three;int four;
} stc;stc __attribute__((packed)) cc;sizeof(cc) = 12  /* ================ test 5 ==============*/typedef struct __attribute__((packed))
{char one;short two;char three;int four;
} stc;
stc cc;sizeof(cc) = 8

 2,编译器内存对齐操作

读者可以先参考博文,了解正常情况下的结构体对齐操作:
C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间_SOC罗三炮的博客-CSDN博客同学可以尝试将char f 注释,最后将得到24,也可以从侧面说明再加上一个char f,其大小肯定大于等于24 byte。Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照。sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3。sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3。s中char ch1;占用 1Byte(存储位置8)的,sizeof的结果是40。最大元素大小的整数倍地址。_联合体占用的内存空间https://blog.csdn.net/luolaihua2018/article/details/115372273?ops_request_misc=&request_id=6d04e4a92dcd4c6585378e324b6f7c4a&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-2-115372273-null-null.268%5Ev1%5Econtrol&utm_term=%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90&spm=1018.2226.3001.4450

 下面将通过一些示例,详情介绍使用__attribute__((packed))和#pragma pack(n)对内存对齐产生的影响:

test1,正常情况下的对齐操作,12 bytes

结构体中,各元素按照顺序摆放,同时需要针对不同大小的成员数据进行不同大小的补齐操作

 test 2,使用__attribute__((packed)),相当于#pragma pack(1),8 bytes (最小)

 #pragma pack(1) 不需要任何内存补齐,没有任何pandding,如下图所示,数据按照成员顺序摆放即可:

 test3, 使用#pragma pack(2),10 bytes

强制使用2 bytes作为结构体中的最大对齐尺寸

 

  test4, 使用#pragma pack(4),12 bytes

  test5, 使用#pragma pack(5) ,12bytes

如果n不是2的次方,即不是1,2,4,8...,则不会生效,并且会报一个warning:

9[Warning] alignment must be a small power of two, not 5 [-Wpragmas]

 test6, 使用#pragma pack(8) ,12bytes 

 正常情况下,结构体c和d中的最大自然对齐尺寸为4,所以编译器会在8和4中选一个最小的对齐尺寸,即为4。

test7,使用__attribute__((packed))对结构体中的成员进行修饰,10 bytes

使用__attribute__((packed))可以指定某个数据成员不需要进行内存对齐,虽然指定了 int类型的four不需要内存对齐,但是结构体中的其他成员仍需遵守内存对齐规则,除了int外,最大的是short类型的two,所以该结构体的最小对齐尺寸为short的大小,即2 bytes,这就是为什么最后的结果为10 bytes,而不是9 bytes.

3,使用armlink 选项 --info=sizes 查看 结构体大小

将示例代码写入str.c,

struct stc
{char one;short two;char three;int four;
} c,d;int main (void)
{c.one=1;return 0;
}

 并按以下命令编译,生成.o文件:

armclang --target=arm-arm-none-eabi -march=armv8-a -c str.c -o str.o

使用armlink的 -info=sizes选项将.o文件的各个属性的数据的大小列出:

armlink file.o --info=sizes

 其中c和d为全局变量,并未对齐初始化,所以编译器会将其初始化为0,即属于 ZI (zero initialize)数据,由下表可知,其大小为12+12=24 bytes:

Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name36          0          0          0         24          0   str.o
---------------------------------------------------------------------------36          0         16          0         24          0   Object Totals

参考文章:

Packing data structures

C/C++计算类/结构体和联合体(union)所占内存大小(内存对齐问题)_联合体占用的内存空间

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

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

相关文章

通付盾发布WAAP白皮书,帮助企业应对数字化转型过程中日益高发的网络安全威胁

简介 企业数字化转型是数字经济发展的重要一环。面对企业数字化转型过程中的安全问题&#xff0c;WAAP白皮书将对攻击方式、攻击量、攻击来源、行业分布等维度对各类攻击进行详细解读&#xff0c;梳理传统Web应用防护能力的不足&#xff0c;分析日益增长的API防护&#xff0c;…

H5学习(三)-- CSS层叠样式表

文章目录 一、简介二、CSS的书写样式1. 行内样式&#xff08;内联样式&#xff09;2. 页内样式3. 外部样式 三、常见的选择器1. 标签选择器2. 类选择器3. id选择器4. 并列选择器5. 复合选择器6. 伪类选择器 一、简介 CSS&#xff08;cascading style sheet&#xff09;是层叠样…

奇舞周刊第497期:解锁 PDF 文件:使用 JavaScript 和 Canvas 渲染 PDF 内容

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 解锁 PDF 文件&#xff1a;使用 JavaScript 和 Canvas 渲染 PDF 内容 最近研究了 Web 的 FileSystemAccess Api&#xff0c;它弥补了 Web 长期以来缺少的能力&#xff1a;操作用户…

QT Creator上位机画波形之Qcharts使用学习

先看一个Qcharts的简单demo Qcharts是QT自带的组件&#xff0c;不需要另外添加文件。 打开QT Creator&#xff0c;新建一个工程&#xff0c;命名可以参考下图&#xff1a; 基类选择QWidget&#xff1a; .pro文件中添加charts模块 main.cpp源码&#xff1a; #include "…

蓝牙音频数据歌词提取器设计方法

v hezkz17进数字音频系统研究开发交流答疑 解决方法&#xff1a; 通过蓝牙接收来自手机音乐播放器的数据&#xff0c;能得到哪些歌曲信息? 如何获取歌曲名&#xff1f;歌词信息&#xff1f; 2023/6/27 10:21:42 通过蓝牙接收手机音乐播放器的数据&#xff0c;可以获取以下歌曲…

JMeter请求头添加删除方法(解决请求头类型冲突)

JMeter请求头添加删除方法&#xff08;解决请求头类型冲突&#xff09; 1. 为什么会有冲突 请求头的Content-Type类型在做上传和请求图片地址是&#xff0c;请求头类型是不一样的 请求图片地址&#xff1a;Content-Type: image/jpeg 一般的Restful接口&#xff1a;Content-Ty…

stm32 + w25qxx + EasyFlash

一&#xff0c;软件介绍 EasyFlash 是一款开源的轻量级嵌入式Flash存储器库&#xff0c;方便实现基于Flash存储器的常见应用开发。适合智能家居、可穿戴、工控、医疗等需要断电存储功能的产品&#xff0c;资源占用低&#xff0c;支持各种 MCU 片上存储器。 [1] 该库目前提供…

Qt/C++编写超精美自定义控件(历时9年更新迭代/超202个控件/祖传原创)

一、前言 无论是哪一门开发框架&#xff0c;如果涉及到UI这块&#xff0c;肯定需要用到自定义控件&#xff0c;越复杂功能越多的项目&#xff0c;自定义控件的数量就越多&#xff0c;最开始的时候可能每个自定义控件都针对特定的应用场景&#xff0c;甚至里面带了特定的场景的…

chatgpt赋能python:如何通过Python赚钱

如何通过Python赚钱 介绍 Python是一种高级编程语言&#xff0c;广泛用于Web开发、数据分析、机器学习等领域。Python具有简单易学、功能强大、易于维护等特点&#xff0c;因此非常受欢迎。而且&#xff0c;Python开源免费&#xff0c;可以在各个平台上运行&#xff0c;不需要…

CH543乐得瑞单C口显示器方案(LDR6020)

首先显示器的种类很多&#xff0c;有桌面显示器&#xff0c;便携显示器&#xff0c;智能显示器&#xff0c;甚至AR眼镜也可以算是一个微型显示器。以往的显示器传输视频信号多为VGA和HDMI,当然DP也有&#xff0c;只是占少数&#xff0c;再早之前还有模拟信号接口等等&#xff0…

第4章 信息系统管理

文章目录 4.1.1 管理基础1 层次结构2 系统管理 4.1.2 规划和组织1 规划模型2 组织模型1&#xff09;业务战略&#xff08;竞争力优势模型&#xff1a;差异化、总成本领先、专注 战略&#xff09;2&#xff09;组织机制战略&#xff08;莱维特钻石模型&#xff1a;信息与控制、人…

进程间通信之共享内存

一、共享内存实现进程间通信的原理 共享内存实际是操作系统在实际物理内存中开辟的一段内存。 共享内存实现进程间通信&#xff0c;是操作系统在实际物理内存开辟一块空间&#xff0c;一个进程在自己的页表中&#xff0c;将该空间和进程地址空间上的共享区的一块地址空间形成…