C/C++结构体内存粒度对齐?结合调试信息来看

简介: C/C++结构体内存粒度对齐?结合调试信息来看

C语言或者C++内存粒度对齐是我们经常提到的问题,而对内存的有效合理的利用,必然会使我们写出来的代码更加高效。这几天写代码过程中出现了一个bug,就是由于内存粒度原因导致,所以总结一下。


首先我们来看一下理论知识(我个人理解的):

结构体中数据成员内存对齐的原则:按照一个结构体中的成员中最大字节数整数倍对齐,比如在x86下,出现int(4字节) char(1字节) double(8字节),则肯定是按照最大的8字节整数倍对齐,8字节就相当于一个基准一样,如果遇到的字节数可以在8字节内存放,那就ok,否则需要重新申请8字节来存放,至于说是8的几倍,那需要看数据存放的顺序,内存满足多插少补的原则。


接下来我就结合代码和调试信息来说明内存粒度对齐的相关问题:

以下提到的测试数据,基于x86

eg1:

typedef struct _STRUCT_A_
{
  char a; 
  int  b;
  char c;
}STRUCT_A;

面对以上的一个结构体,初学者可能认为是1+4+1总共6个字节。那么我们来实际看看它的大小究竟是多少

image.png

它是12字节的大小,那么我们再来看看它的内存分布情况:

image.png

确实初始化为12个字节的0,那我们赋值字符a,int值5以及字符c之后,内存中又是如何存放的呢?

image.png

那我们可以如何优化这个结构体,让他它占有更少的内存呢?我们可以写成以下两种形式:

eg2:

typedef struct _STRUCT_A_
{
  int  b;
  char a; 
  char c;
}STRUCT_A;
typedef struct _STRUCT_A_
{
  char a; 
  char c;
  int  b;
}STRUCT_A;

我们再来看看内存中的情况以及结构体大小为多少:

image.png


image.png


以上证实了我们一开始的说法,当char存放占4字节基准为1字节,接下来的成员还是char,拿它就可以继续存放,存放第二个char之后,还有2字节空余,但是遇到了4字节的int,不够了,只能存放在另外的4字节内存中了。

那我们有没有办法,就是不管我们怎么存放,让它都有固定的内存对齐粒度呢?当然是可以的:

eg3:

#pragma pack(push)
#pragma pack(2)
typedef struct _STRUCT_A_
{
  char a;
  int  b;
  char c;
}STRUCT_A;
#pragma pack(pop)

我们可以使用以上代码,使内存粒度对齐按照2字节对齐,我们来验证一下:

image.png

image.png

当然也可以按照1字节对齐,只需要修改pack中的值为1,如下:

eg4:

#pragma pack(push)
#pragma pack(1)
typedef struct _STRUCT_A_
{
  char a;
  int  b;
  char c;
}STRUCT_A;
#pragma pack(pop)

image.png

image.png

但是我们在实际情况中,可能会遇到更加复杂点的情况,比如结构体的成员是结构体或者联合体。这种情况又是如何对齐的呢?我们来看一个稍微复杂一点的结构体:

eg5:

typedef struct _STRUCT_A_
{
  uint32_t a;
  uint32_t b;
}STRUCT_A;
typedef union _UNION_A_ {
  char*  m1;
  STRUCT_A m2;
}UNION_A;
typedef struct _STRUCT_B_
{
  UNION_A a;
  UNION_A  b;
  UNION_A c;
  UNION_A d;
  uint16_t e;//2字节
}STRUCT_B;

按照我们的想法,那应该UNION_A所占内存大小为8字节,那么STRUCT_B应该按照8字节对齐,那么5个数据成员,大小应该使5*8=40字节,但是实际情况好像并不是这样:

image.png

怎么会出现36这个情况呢?原因出现在STRUCT_A中,虽然unionA的大小确实为8字节,但是其8字节的主要原因是STRUCT_A是8字节,那STRUCT_A中是两个4字节的成员,所以内存粒度对齐还是4字节,这样就不难理解最后的STRUCT_B为36字节了,其中前4个成员是32字节,最后一个是2字节,但是按照4字节对齐,所以是36字节。


“山不向我走来,我便向它走去”


目录
相关文章
|
13天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
13天前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【11月更文挑战第6天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
|
14天前
|
存储 Java 程序员
结构体和类的内存管理方式在不同编程语言中的表现有何异同?
不同编程语言中结构体和类的内存管理方式既有相似之处,又有各自的特点。了解这些异同点有助于开发者在不同的编程语言中更有效地使用结构体和类来进行编程,合理地管理内存,提高程序的性能和可靠性。
22 3
|
16天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
16天前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
111 21
|
1月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
40 0
【C++打怪之路Lv6】-- 内存管理
|
1月前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【10月更文挑战第8天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
|
1月前
|
C++
C/C++内存管理(下)
C/C++内存管理(下)
49 0