21-C语言的结构体尺寸——地址对齐问题

news2024/9/21 14:51:14

21-C语言的结构体尺寸——地址对齐问题

文章目录

  • 21-C语言的结构体尺寸——地址对齐问题
    • 一、CPU 字长
    • 二、 地址对齐
      • 2.1 原理和原因
      • 2.2 地址对齐的主要思想
      • 2.3 示例代码说明地址对齐
    • 三、普通变量的M值
      • M值的计算规则
      • 例子
    • 四、手动干预M值
      • 4.1 规则
      • 4.2 例子
    • 五、结构体的M值
      • 5.1 例子分析
        • 5.1.1 分析步骤
        • 5.1.2 填充和对齐
      • 5.2 结构体大小计算
    • 六、结构体得可移植性:统一结构体大小的策略
      • 6.1 方法1:使用`__attribute__((packed))`进行压实
      • 6.2 方法2:对每一个成员进行单独对齐设置
      • 6.3 示例代码

一、CPU 字长

字长是指处理器在执行一条指令时所能处理的最大数据位数,主要影响处理器的运算能力和内存寻址能力。字长由处理器的设计和所使用的系统位数共同决定。

  • 32位系统:每次最多可以处理32位(4字节)数据。
  • 64位系统:每次最多可以处理64位(8字节)数据。
    在这里插入图片描述

处理器的字长直接影响到计算机系统的性能和处理能力。64位处理器能够处理更多数据和地址空间,因此在运行大型应用程序和处理大数据集时具有优势。

二、 地址对齐

地址对齐是指在内存中存放数据时,将数据存放在地址能够被特定字长整除的位置上。地址对齐的主要目的是提高CPU访问内存的效率。

2.1 原理和原因

32位CPU的地址对齐

  • 存取内存策略:32位CPU每次存取内存数据时,最少存取4个字节(即32位)。
  • 对齐方式:按4字节对齐,数据的起始地址通常是4的倍数。

  • 高效读取数据:为了提高读取数据的效率,编译器会尽量将变量放在一个4字节单元内。如果变量较大,无法放入一个4字节单元,编译器则尽可能将变量放入两个4字节单元内。总的来说,使用最少的单元来存放数据是编译器的优化策略。
  • 节省时间:通过对齐方式存放数据,可以减少CPU访问内存的次数,从而提高执行效率。

在这里插入图片描述

在这里插入图片描述

2.2 地址对齐的主要思想

  • 提高CPU运行效率:通过减少CPU读取和写入内存的次数来提高运行效率。
  • 内存单元使用:一个数据尽可能使用一个单元来存放,避免使用多个单元。两个单元能放下的数据绝不用三个单元。这种优化策略是为了减少内存访问时间,提高系统整体性能。

2.3 示例代码说明地址对齐

#include <stdio.h>

struct Example {
    char a;
    int b;
    short c;
};

int main() {
    struct Example example;
    printf("Size of struct Example: %lu\n", sizeof(example));
    printf("Address of a: %p\n", (void*)&example.a);
    printf("Address of b: %p\n", (void*)&example.b);
    printf("Address of c: %p\n", (void*)&example.c);
    return 0;
}

在这个示例中,结构体 Example 包含三个成员:char aint bshort c。编译器会自动对齐这些成员,以提高内存访问效率。具体对齐方式取决于编译器和处理器的对齐规则。

解释

  • sizeof(example) 可能大于所有成员大小之和,这是由于编译器插入了填充字节来对齐数据。
  • example.a 的地址通常是 0x0 开始(相对结构体起始地址)。
  • example.b 的地址通常是4的倍数。
  • example.c 的地址通常是2的倍数,但在 int 后面也会对齐到 4 的倍数。

三、普通变量的M值

概念:一个数据的大小是固定的(例如,整型),如果这个数据所存放的地址能够被某个数整除(如4),那么这个数就称为该数据的M值。M值与系统的字长和数据大小有关

M值的计算规则

可以根据具体的系统字长和数据的大小来估算M值得大小.

例如:

  • 整型变量 int i
    • 占用 4 字节
    • 如果 i 存放在能被4整除的地址下,则其地址对齐,M值为4
  • 字符变量 char c
    • 占用 1 字节
    • 如果 c 存放在能被1整除的地址下,则其地址对齐,M值为1
  • 短整型变量 short s
    • 占用 2 字节
    • 如果 s 存放在能被2整除的地址下,则其地址对齐,M值为2
  • 双精度浮点变量 double d
    • 占用 8 字节
    • 如果 d 存放在能被4整除的地址下,则其地址对齐,M值为4(注意:在某些系统中,双精度浮点数的对齐要求可能为8)
  • 浮点变量 float f
    • 占用 4 字节
    • 如果 f 存放在能被4整除的地址下,则其地址对齐,M值为4

例子

假设有以下变量的地址:

int i;  // i 占用 4 字节
char c; // c 占用 1 字节
short s; // s 占用 2 字节
double d; // d 占用 8 字节
float f; // f 占用 4 字节

printf("i: %p\n", (void*)&i);
printf("s: %p\n", (void*)&s);
printf("c: %p\n", (void*)&c);
printf("d: %p\n", (void*)&d);
printf("f: %p\n", (void*)&f);

假设输出地址如下:

i: 0x7fffc900a298
s: 0x7fffc900a296
c: 0x7fffc900a295
d: 0x7fffc900a2a0
f: 0x7fffc900a29c
  • i 的地址 0x7fffc900a298 是4的倍数,M值为4。
  • s 的地址 0x7fffc900a296 是2的倍数,M值为2。
  • c 的地址 0x7fffc900a295 是1的倍数,M值为1。
  • d 的地址 0x7fffc900a2a0 是4的倍数,M值为4(假设系统对齐要求为4)。
  • f 的地址 0x7fffc900a29c 是4的倍数,M值为4。

四、手动干预M值

可以使用 __attribute__((aligned(n))) 来手动指定变量的对齐方式。这是GNU特定的语法,属于C语言标准的扩展。

char c __attribute__((aligned(16)));

4.1 规则

  • __attribute__ 前后都有两个下划线。
  • __attribute__ 右边由两对小括号 (( ))
  • __attribute__ 还支持其他设置。

注意事项

  • 提升不降低:一个变量的M值只能提升,不能降低。即,不能手动设置对齐方式使其小于编译器默认的对齐方式。
  • 2的幂次:M值必须是2的幂次,如1, 2, 4, 8, 16, 32, 64等。

4.2 例子

#include <stdio.h>

int main() {
    // 定义变量
    int i;  // 默认对齐,M值为4
    char c __attribute__((aligned(16)));  // 强制对齐16字节,M值为16

    // 打印地址
    printf("Address of i: %p\n", (void*)&i);
    printf("Address of c: %p\n", (void*)&c);

    return 0;
}

在这个例子中,c 被强制对齐到16字节,这意味着其地址将是16的倍数,而 i 则默认对齐4字节。编译器会在生成的二进制文件中添加适当的填充字节以满足这些对齐要求。

五、结构体的M值

结构体的M值取决于其成员中M值最大的成员。为了确保结构体的地址对齐和整体大小满足对齐要求,需要考虑以下几点:

  1. 结构体的M值:结构体中有多个成员,取决于成员中M值最大的成员。
  2. 结构体的地址对齐:结构体的地址必须能被结构体的M值整除。
  3. 结构体的尺寸:结构体的大小等于成员中宽度最宽的成员的倍数。

5.1 例子分析

typedef struct node {
    int i;          // 4 字节
    char c;         // 1 字节
    short s;        // 2 字节
    double d;       // 8 字节
    long double ld; // 16 字节
    float f;        // 4 字节
} Node;
5.1.1 分析步骤
  1. 成员变量的M值

    • int i 的M值为4。
    • char c 的M值为1。
    • short s 的M值为2。
    • double d 的M值为8。
    • long double ld 的M值为16。
    • float f 的M值为4。
  2. 结构体的M值

    • 取决于成员中M值最大的成员,即 long double ld 的M值,为16。
    • 所以,结构体 Node 的M值为16。
  3. 成员变量的对齐

    • int i:占用4字节,对齐4字节。
    • char c:占用1字节,对齐1字节(紧接在i后面,需要考虑填充)。
    • short s:占用2字节,对齐2字节(紧接在c后面,需要考虑填充)。
    • double d:占用8字节,对齐8字节(紧接在s后面)。
    • long double ld:占用16字节,对齐16字节。
    • float f:占用4字节,对齐4字节。
5.1.2 填充和对齐

假设在64位系统中,编译器的默认对齐方式为8字节:

  1. int i:4字节,占用地址偏移0到3。
  2. char c:1字节,占用地址偏移4,填充3字节使short s对齐。
  3. short s:2字节,占用地址偏移8到9,填充6字节使double d对齐。
  4. double d:8字节,占用地址偏移16到23。
  5. long double ld:16字节,占用地址偏移24到39。
  6. float f:4字节,占用地址偏移40到43,填充4字节使结构体大小为48字节(最宽成员倍数)。

5.2 结构体大小计算

  • int i:4字节,占用地址偏移0-3。
  • char c:1字节,占用地址偏移4。
  • 填充:3字节,占用地址偏移5-7。
  • short s:2字节,占用地址偏移8-9。
  • 填充:6字节,占用地址偏移10-15。
  • double d:8字节,占用地址偏移16-23。
  • long double ld:16字节,占用地址偏移24-39。
  • float f:4字节,占用地址偏移40-43。
  • 填充:4字节,占用地址偏移44-47。

结果

在64位系统中,该结构体的大小为48字节,能够被long double的对齐要求整除(16字节)。

代码示例

#include <stdio.h>

typedef struct node {
    int i;
    char c;
    short s;
    double d;
    long double ld;
    float f;
} Node;

int main() {
    Node node_instance;

    printf("Size of struct Node: %lu\n", sizeof(Node));
    printf("Address of i: %p\n", (void*)&node_instance.i);
    printf("Address of c: %p\n", (void*)&node_instance.c);
    printf("Address of s: %p\n", (void*)&node_instance.s);
    printf("Address of d: %p\n", (void*)&node_instance.d);
    printf("Address of ld: %p\n", (void*)&node_instance.ld);
    printf("Address of f: %p\n", (void*)&node_instance.f);

    return 0;
}

这段代码会打印出结构体Node的大小和各成员变量的地址,通过这些地址可以验证结构体的对齐和填充。
在这里插入图片描述

六、结构体得可移植性:统一结构体大小的策略

为了确保结构体在不同操作系统(不同位数)上的大小保持一致,可以采用以下两种方法:

6.1 方法1:使用__attribute__((packed))进行压实

通过使用__attribute__((packed)),可以使编译器不在成员之间填充任何空隙,确保结构体的每个成员紧密排列。

typedef struct node {
    int i;
    char c;
    short s;
    double d;
    long double ld;
    char kk;
    float f;
} __attribute__((packed)) Node;

解释

  • __attribute__((packed)):告诉编译器不要在成员之间插入任何填充字节。这样可以确保结构体在不同平台上的大小一致,但是可能会导致访问非对齐数据的性能下降。

6.2 方法2:对每一个成员进行单独对齐设置

通过对每个成员进行单独对齐设置,可以确保结构体在不同平台上的对齐方式一致。

struct node {
    int i __attribute__((aligned(4)));
    char c __attribute__((aligned(1)));
    short s __attribute__((aligned(2)));
    double d __attribute__((aligned(4)));
    long double ld __attribute__((aligned(4)));
    char kk __attribute__((aligned(1)));
    float f __attribute__((aligned(4)));
};

解释

  • __attribute__((aligned(n))):指定每个成员的对齐方式为n字节。这可以确保结构体成员在不同平台上的对齐方式一致,从而确保结构体大小一致。

注意事项:

  1. 结构体的大小:取决于多个因素,包括地址对齐M值(最大对齐值)等。默认情况下,结构体的大小为成员中最大M值的倍数
  2. 可移植性:为了实现结构体的可移植性,需要使用可移植类型并结合__attribute__机制对结构体进行压实。
  3. 性能:使用__attribute__((packed))可能会导致访问非对齐数据的性能下降,具体影响取决于具体平台。

6.3 示例代码

以下示例展示了如何使用两种方法创建可移植的结构体,并验证其大小和成员偏移:

#include <stdio.h>

typedef struct node_packed {
    int i;
    char c;
    short s;
    double d;
    long double ld;
    char kk;
    float f;
} __attribute__((packed)) NodePacked;

struct node_aligned {
    int i __attribute__((aligned(4)));
    char c __attribute__((aligned(1)));
    short s __attribute__((aligned(2)));
    double d __attribute__((aligned(4)));
    long double ld __attribute__((aligned(4)));
    char kk __attribute__((aligned(1)));
    float f __attribute__((aligned(4)));
};

int main() {
    NodePacked packed_instance;
    struct node_aligned aligned_instance;

    printf("Size of packed struct: %lu\n", sizeof(NodePacked));
    printf("Size of aligned struct: %lu\n", sizeof(struct node_aligned));

    printf("Offsets in packed struct:\n");
    printf("Offset of i: %lu\n", offsetof(NodePacked, i));
    printf("Offset of c: %lu\n", offsetof(NodePacked, c));
    printf("Offset of s: %lu\n", offsetof(NodePacked, s));
    printf("Offset of d: %lu\n", offsetof(NodePacked, d));
    printf("Offset of ld: %lu\n", offsetof(NodePacked, ld));
    printf("Offset of kk: %lu\n", offsetof(NodePacked, kk));
    printf("Offset of f: %lu\n", offsetof(NodePacked, f));

    printf("Offsets in aligned struct:\n");
    printf("Offset of i: %lu\n", offsetof(struct node_aligned, i));
    printf("Offset of c: %lu\n", offsetof(struct node_aligned, c));
    printf("Offset of s: %lu\n", offsetof(struct node_aligned, s));
    printf("Offset of d: %lu\n", offsetof(struct node_aligned, d));
    printf("Offset of ld: %lu\n", offsetof(struct node_aligned, ld));
    printf("Offset of kk: %lu\n", offsetof(struct node_aligned, kk));
    printf("Offset of f: %lu\n", offsetof(struct node_aligned, f));

    return 0;
}

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

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

相关文章

每天一个设计模式之职责链模式(第一天)

特别感谢刘伟老师&#xff0c;看他的书我学到了很多东西&#xff0c;从今天开始我要开始更新啦&#xff01; 在csdn个人博客来总结知识&#xff0c;把他们变成自己的能力。 对三&#xff0c;要不起&#xff0c;张三李四王五几个人在玩斗地主&#xff0c;过过过&#xff0c;一…

谷粒商城实战笔记-错误记录-启动失败

文章目录 一&#xff0c;lombok报错二&#xff0c;Output directory is not specified 一&#xff0c;lombok报错 java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok …

靶场实战 _ ATTCK 实战 Vulnstack 红队

环境配置 网络拓扑图 (仅供参考) 攻击机&#xff1a;kali ip:192.168.111.5靶机&#xff1a;web-centos 外网ip:192.168.111.10 内网ip:192.168.93.100web1-ubuntu ip: 192.168.93.120PC ip: 192.168.93.30win 2008 ip:192.168.93.20win 2012 ip:192.168.93.10 信息搜集 端口…

【QT】常用控件(概述、QWidget核心属性、按钮类控件、显示类控件、输入类控件、多元素控件、容器类控件、布局管理器)

一、控件概述 Widget 是 Qt 中的核心概念&#xff0c;英文原义是 “小部件”&#xff0c;此处也把它翻译为 “控件”。控件是构成一个图形化界面的基本要素。 像上述示例中的按钮、列表视图、树形视图、单行输入框、多行输入框、滚动条、下拉框都可以称为 “控件”。 Qt 作为…

世界渲染大赛含金量高吗?含金量怎么样?水平要求?

世界渲染大赛&#xff0c;作为全球3D艺术与渲染领域的顶级赛事&#xff0c;以其高含金量和专业水平要求而闻名。这一赛事不仅吸引了世界各地的专业3D艺术家、设计师和技术爱好者的积极参与&#xff0c;更以其严格的评审标准和创新性的主题设置&#xff0c;确保了参赛作品的高质…

C#初级——条件判断语句、循环语句和运算符

条件判断语句 简单的条件判断语句&#xff0c;if()里面进行条件判断&#xff0c;如果条件判断正确就执行语句块1&#xff0c;如果不符合就执行语句块2。 if (条件判断) { 语句块1 } else { 语句块2 } int age 18;if (age < 18){Console.WriteLine("未…

一天搞定React(3)——Hoots组件【已完结】

Hello&#xff01;大家好&#xff0c;今天带来的是React前端JS库的学习&#xff0c;课程来自黑马的往期课程&#xff0c;具体连接地址我也没有找到&#xff0c;大家可以广搜巡查一下&#xff0c;但是总体来说&#xff0c;这套课程教学质量非常高&#xff0c;每个知识点都有一个…

git sendemail使用

教程参考&#xff1a; git-send-email - 以电子邮件形式发送补丁集 1、安装git-email 2、配置 SMTP 服务器 git config --global sendemail.smtpserver smtp.163.com git config --global sendemail.smtpserverport 465 git config --global sendemail.smtpuser xxxxxx163.c…

Godot入门 04平台设计

新建创景&#xff0c;添加AnimatableBody2D节点。 添加Sprite2D节点 拖动图片 剪裁图片&#xff0c;吸附模式&#xff1a;像素吸附 添加CollisionShape2D&#xff0c;设置实际形状为矩形 重命名AnimatableBody2D节点为Platform&#xff0c;保存场景&#xff0c;拖动platform场景…

Docker NameSpace隔离

1、dd命令&#xff1a;dd 可从标准输入或文件中读取数据&#xff0c;根据指定的格式来转换数据&#xff0c;再输出到文件、设 备或标准输出 功能&#xff1a;用于读取、转换并输出数据 语法&#xff1a;dd OPTION 参数 1、 if文件名&#xff1a;输入文件名&#xff0c;默认为…

【第四天】计算机网络知识 HTTP1.0,HTTP1.1与HTTP2.0的区别 HTTP3.0

HTTP1.0&#xff0c;HTTP1.1与HTTP2.0的区别 HTTP1.0 默认是短链接&#xff0c;可以强制开启长连接。HTTP1.1默认长连接。HTTP2.0采用多路复用。 HTTP1.0&#xff1a; 默认使用短链接&#xff0c;每次请求都需要建立一个TCP连接。它可以设置&#xff1a;Connection: keep-aliv…

内网对抗-隧道技术篇防火墙组策略FRPNPSChiselSocks代理端口映射C2上线

知识点&#xff1a; 1、隧道技术篇-传输层-工具项目-Frp&Nps&Chisel 2、隧道技术篇-传输层-端口转发&Socks建立&C2上线Frp Frp是专注于内网穿透的高性能的反向代理应用&#xff0c;支持TCP、UDP、HTTP、HTTPS等多种协议。可以将内网服务以安全、便捷的方式通过…

H264编码标准环路滤波原理

方块效应产生原因 原因 1&#xff1a;最重要的一个原因是基于块的帧内和帧间预测残差的 DCT 变换。变换系数的量化过程相对粗糙&#xff0c;因而反量化过程恢复的变换系数带有误差&#xff0c;会造成在图像块边界上的视觉不连续。原因 2&#xff1a;其次原因自于运动补偿预测。…

FastAPI(七十九)实战开发《在线课程学习系统》接口开发-- 加入课程和退出课程

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 加入课程 我们先看下加入课程 1.是否登录 2.课程是否存在 3.是否已经存在 4.添加 首先实现逻辑 def get_student_course(db: Session, course: int…

4.Jmeter接口性能测试

性能测试的指标: 多:并发量(用户数-系统用户数-影响到磁盘、在线用户数、并发用户数) 系统用户数:软件系统注册的用户总数(要注意初始化环境) 在线用户数:某段视角内访问用户数,这些用户只是咋先,不一定同时做某一件事情。(初始化环境(warm up热机:让…

DL/T645、IEC104转BACnet网关实现实时数据采集

BA102网关是钡铼技术专为实现电力协议DL/T645、IEC104与楼宇自控协议BACnet相互转化而研发的。它下行采集支持Modbus RTU、Modbus TCP、DL/T645、IEC104等协议&#xff0c;上行转发则支持BACnet IP和BACnet MS/TP协议&#xff0c;从而实现了电力协议与楼宇自控协议之间的相互转…

CJS与ESM:CJS

模块化方案 历史上&#xff0c;JavaScript 一直没有模块&#xff08;module&#xff09;体系&#xff0c;无法将一个大程序拆分成互相依赖的小文件&#xff0c;再用简单的方法拼装起来。其他语言都有这项功能&#xff0c;比如 Ruby 的require、Python 的import&#xff0c;甚至…

【算法】分布式共识Paxos

一、引言 在分布式系统中&#xff0c;一致性是至关重要的一个问题。Paxos算法是由莱斯利兰伯特&#xff08;Leslie Lamport&#xff09;在1990年提出的一种解决分布式系统中一致性问题的算法。 二、算法原理 Paxos算法的目标是让一个分布式系统中的多个节点就某个值达成一致。算…

LoRaWAN设备的两种入网方式(ABP和OTAA)

目录 一、OTAA 1、名词解释 2、入网流程 二、ABP 三、两种入网方式的比较 一、OTAA 1、名词解释 &#xff08;1&#xff09;AppEUI&#xff1a;64位&#xff08;8字节&#xff09;的唯一标识符&#xff0c;用于标识特定的应用程序或组织&#xff08;如果用的是chirpstac…

Linux的yum源安装MySQL5.7

linux的yum源安装MySQL5.7 一、MySQL 1、简介 MySQL 是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典公司 MySQL AB 开发&#xff0c;后来被 Oracle Corporation 收购。它是一个开源软件&#xff0c;提供了高效、稳定和可靠的数据管理解决…