C++ 结构体对齐详解

news/2024/6/28 2:05:37 标签: c++, 开发语言, 数据结构

目录

前言

一、为什么要对结构体进行对齐操作?

二、基本概念

三、 对齐规则

四、示例讲解

1.简单的变量对齐

2.结构体包含有结构体的对齐

结构体成员详细解析

五、使用指令改变对齐方式

__attribute__((packed))

#pragma pack(push, n)

#pragma pack(pop)

六、总结


前言

在C++中,结构体(struct)的对齐是指编译器为确保结构体成员在内存中的地址满足特定的对齐要求而进行的调整。对齐有助于提高访问速度,但同时也可能导致内存空间的浪费。


一、为什么要对结构体进行对齐操作?

结构体对齐的主要目的是优化内存访问速度提高内存利用率

二、基本概念

对齐(Alignment):指数据在内存中按照某种边界方式排列。例如,4字节的整数通常按4字节边界对齐。

填充(Padding):为了满足对齐要求而在数据之间插入的无意义的字节。

边界(Alignment Boundary):存储单元的起始地址必须是其大小的整数倍,例如,4字节的整数应该存储在能被4整除的地址上。

三、 对齐规则

编译器通常遵循以下规则来对齐结构体:

  • 每个成员变量的地址都是其自身大小的整数倍。
  • 结构体的总大小是其最大成员大小的整数倍。
  • 编译器可能会插入填充字节以满足上述对齐要求。

四、示例讲解

1.简单的变量对齐

struct Example {
    char a;   // 1 byte
    int b;    // 4 bytes
    short c;  // 2 bytes
};

编译器将如何对齐和填充这个结构体?假设默认对齐是4字节:

  • char a 占用1字节,开始地址0x00。
  • int b 需要4字节对齐,下一个可以被4整除的地址是0x04,因此在 a 后面插入3个填充值。
  • short c 需要2字节对齐,b 占用4个字节后,c 可以直接放在下一个地址0x08。

所以结构体在内存中的布局如下:

以结构体在内存中的布局如下:

地址内容
0x00a
0x01填充
0x02填充
0x03填充
0x04b
0x05b
0x06b
0x07b
0x08c
0x09c

总大小是10字节,但为了满足最大成员(int)的对齐要求,编译器可能会将结构体的总大小调整为12字节,使其成为4的倍数。

2.结构体包含有结构体的对齐

编译器将根据每个类型的自然对齐需求来安排各个成员的位置,以便优化内存访问。

#include <iostream>

struct InnerStruct {
    char a;   // 1字节
    int b;    // 4字节
};

struct OuterStruct {
    char c;             // 1字节
    InnerStruct inner;  // 包含内部结构体
    double d;           // 8字节
};

结构体成员详细解析

  1. 内部结构体 InnerStruct

    • char a :占用1字节。
    • int b :通常情况下,占用4字节。
  2. 外部结构体 OuterStruct

    • char c :占用1字节。
    • InnerStruct inner :包含了 InnerStruct 类型的内部结构体。
    • double d :占用8字节。
  • 内部结构体 InnerStruct 的对齐

    • char a 占用1字节。由于 int b 需要4字节对齐,所以 a 后面会有3字节的填充,使得 b 能够对齐到下一个4的倍数地址上。
    • 因此,InnerStruct 的总大小将是8字节(1字节 a + 3字节填充 + 4字节 b)。
  • 外部结构体 OuterStruct 的对齐

    • char c 占用1字节。为了对齐 InnerStructc 后面会有3字节的填充。
    • InnerStruct inner 将从4字节边界开始,总共占用8字节(如上所述)。
    • double d 通常需要8字节对齐,因此在 InnerStruct inner 后面可能会有额外的填充字节。

下面是这两个结构体在默认对齐规则下的内存布局示意图:总结20字节

OuterStruct:
| c (1B) | padding (3B) | InnerStruct (8B) | double d (8B) |

InnerStruct:
| a (1B) | padding (3B) | b (4B) |

五、使用指令改变对齐方式

可以使用编译器特定的指令或预处理指令来改变结构体的对齐方式。

__attribute__((packed))

例如,在GCC中,可以使用 __attribute__((packed)) 来消除填充:

struct ExamplePacked {
    char a;
    int b;
    short c;
} __attribute__((packed));

在MSVC中,可以使用 #pragma pack(push, n)#pragma pack(pop)

#pragma pack(push, 1)
struct ExamplePacked {
    char a;
    int b;
    short c;
};
#pragma pack(pop)

#pragma pack(push, n)

  • 功能#pragma pack(push, n) 将当前的对齐设置压入一个栈中,并将对齐值设置为 n
  • 参数
    • n:指定新的对齐值(通常是1、2、4、8或16),表示数据成员应该按照 n 字节边界进行对齐。

#pragma pack(pop)

  • 功能#pragma pack(pop) 用于从栈中弹出之前保存的对齐设置,并恢复该设置。

这样做的结果是结构体成员紧密排列,没有填充字节,但会导致性能下降,因为访问未对齐的数据可能需要更多的处理时间。

六、总结

  • 默认对齐:通常情况下,最好使用编译器的默认对齐方式,以获得最佳性能。
  • 手动对齐:只有在明确知道需要节省内存,并且能接受性能损失的情况下才使用手动对齐。

理解和控制结构体的对齐对于编写高效、可靠的C++程序非常重要。希望这篇详解能够帮助你更好地掌握结构体对齐的概念和应用。


http://www.niftyadmin.cn/n/5534622.html

相关文章

【STM32+FPGA】先进算力+强安全+边缘AI,64位STM32MP2聚焦工业4.0应用

工业应用数字化和智能化程度&#xff0c;是衡量新质生产力的重要标准。STM32最新一代64位微处理器STM32MP2凭借先进算力、丰富接口和高安全性&#xff0c;为高性能和高度互联的工业4.0应用赋能。 STM32MP2四大关键特性&#xff0c;为工业4.0应用赋能 STM32MP2系列的第一颗产品S…

宁波银行票据案例解读,要注入科技赋能票据新形式

随着科技的飞速发展&#xff0c;金融行业正迎来一场前所未有的变革。作为一家以科技创新为驱动的现代化银行&#xff0c;宁波银行在这场变革中积极探索&#xff0c;宁波银行票据案例之后持续通过引入先进技术&#xff0c;为客户提供更加高效、智能的金融服务。 宁波银行推出的…

Java程序之多线程顺序打印 ABC

题目&#xff1a; 按顺序打印 ABC ABC ABC ...。有这么一个多线程场景问题&#xff1a;有三个线程&#xff0c;线程1执行(输出A)完成之后线程2执行(输出B)&#xff0c;线程2执行完之后线程3执行(输出C)&#xff0c;线程3执行完成之后线程1执行...&#xff0c;整体循环50次&…

【C语言】函数执行背后的秘密:函数栈帧的创建和销毁超详解

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1. 什么是函数栈帧 2. 理解函数栈帧能解决什么问题呢&#xff1f; 3. 函数栈帧的创建和销毁解析 3.1 什么是栈&#xff1f; 3.2 认识相关寄存器和汇编指…

【PA交易】BackTrader(二): 同时使用tick和K线数据

前言 本文是BackTrader数据源系列的中篇。 文内会省略大量上篇文章的代码内容&#xff0c;直接阅读会产生轻微困惑。阅读前请务必完整理解并完成了Quickstart Guide - Backtrader 并阅读和实操过本系列第一篇&#xff1a; 【PA交易】BackTrader(一): 如何使用实时tick数据和…

详解 ClickHouse 的查询优化

一、单表查询 1. 使用 prewhere 替代 where prewhere 和 where 语句的作用相同&#xff0c;都是用来过滤数据prewhere 和 where 语句的不同在于&#xff1a; prewhere 只支持 MergeTree 族系列引擎的表prewhere 首先会读取指定的列数据来判断数据过滤&#xff0c;等待数据过滤…

GD32 串口接受异常的几个原因

前面我们介绍过GD32 485发送时出现异常的最常见原因&#xff0c;有小伙伴反馈想要知道GD32 串口接受异常的可能原因&#xff0c;今天我们就来安排。 一、波特率异常导致收发出错 我们知道&#xff0c;串口是异步通讯接口&#xff0c;通讯双方或者多方都需要工作在相同波特率下…

Kotlin基础——Typeclass

高阶类型 如在Iterable新增泛型方法时 interface Iterable<T> {fun filter(p: (T) -> Boolean): Iterable<T>fun remove(p: (T) -> Boolean): Iterable<T> filter { x -> !p(x) } }对应的List、Set实现上述方法时仍需要返回具体的类型 interfac…