C/C++结构体内存对齐的一些思考

1 篇文章 0 订阅
1 篇文章 0 订阅

在C++中,结构体的内存对齐是为了提高访问结构体成员变量的效率和保证硬件的要求


C++ 结构体内存对齐的示例代码

#include <iostream>

struct Test_Struct {
    char c;
    int i;
    double d;
};

int main() {
    std::cout << "Size of Test_Struct: " << sizeof(Test_Struct) << " bytes" << std::endl;
    return 0;
}

运行结果:
在这里插入图片描述


C/C++结构体内存对齐的原则

这里直接给出原则如下:

  • 数据成员按照声明顺序依次排列每个数据成员都会占用相应的内存空间
  • 结构体的大小是其数据成员大小的总和,但并不等于所有数据成员大小之和
  • 数据成员的对齐要求是根据它们的类型而定。通常,基本类型(如int、char、double等)的对齐要求是其自身大小或操作系统的最小字节对齐数中较小的那个
  • 如果结构体的某个数据成员的大小超过了默认的对齐要求,那么编译器会进行填充以满足对齐要求,从而保证结构体整体的对齐。
  • 编译器可能会在结构体的末尾添加额外的填充字节,以保证结构体的整体对齐。
  • 可以使用C++中的sizeof关键字来获取一个结构体的大小,它所返回的值即为该结构体的字节大小。

需要注意的是,结构体的内存对齐问题在不同的编译器和平台上可能会有所差异,可以使用预处理指令#pragma pack或编译选项来修改默认的内存对齐方式。为了保证代码的可移植性,可以使用固定大小的数据类型(例如uint32_t、int64_t等)来代替原生类型,以确保在不同平台上具有相同的内存布局和对齐方式。


结合汇编代码分析结构体的内存对齐问题

利用compiler explorer在线查看代码的汇编形式,汇编代码如下:

.LC0:
        .string "Size of MyStruct: "
.LC1:
        .string " bytes"
main:
        push    rbp
        mov     rbp, rsp
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, 16
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned long)
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        mov     eax, 0
        pop     rbp
        ret

首先在数据段部分,.LC0和.LC1,存储了输入和输出的字符串内容,这个没什么可解释的(C/C++数据段主要是用来保存全局变量和静态变量的内存区域,此外还有文本段、栈Stack、堆Heap、常量存储区constant storage area)。

其次,push rbp保存调用者的栈帧指针,mov rbp, rsp将当前栈指针(rsp)赋值给基指针(rbp),建立新的栈帧;mov esi, OFFSET FLAT:.LC0将字符串.LC0的地址保存到寄存器esi中。

mov edi, OFFSET FLAT:_ZSt4cout将全局对象 _ZSt4cout 的地址保存到寄存器edi中。

mov esi, 16将常数值16保存到寄存器esi中,即结构体的大小。

mov rdi, rax将输出操作符结果的返回值保存到寄存器rdi中,作为下一次输出操作的目标。

mov esi, OFFSET FLAT:.LC1 将字符串.LC1的地址保存到寄存器esi中。

mov rdi, rax 将上一次输出的返回值保存到寄存器rdi中,作为下一次输出的输出流对象。

最后,将eax寄存器的值设置为0:mov eax, 0;弹出栈中的rbp值:pop rbp;返回到调用函数的地址:ret

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值