在C++中,结构体的内存对齐是为了
提高访问结构体成员变量的效率和保证硬件的要求
。
结构体对齐 C/C++
- C++ 结构体内存对齐的示例代码
- C/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
。