Linux C/C++学习5:内存对齐问题案例分析和共享
案例背景
笔者小白一枚,在开发LogViewer工具时,遇到了内存对齐的问题。网上有很多关于内存对齐问题的博客,本文仅分享一下实际案例,希望能帮到读者去理解内存对齐。
LogViewer是一款自研的协议栈信令解析工具,协议栈通过TCP将信令上传给LogViewer进行解析(信令本质上就是C语言中的结构体)。笔者所接触的4G协议栈是基于x86系统开发的,而5G协议栈是基于x64系统开发的。因此,5G协议栈结构体的存储默认是8字节对齐的,而LogViewer在没有更改配置的情况下依旧按照4字节对齐去解析,于是导致数据异常。
为了方便阐述内存对齐的问题,笔者将具体信令抽象成如下MEM_ALIGN_DEMO_T结构体,读者可忽略信令及其内容的实际意义。
typedef struct mem_align_demo_t
{
unsigned short A; // 2bytes
unsigned short B; // 2bytes
unsigned long C; // 8bytes
unsigned long D; // 8bytes
unsigned char E; // 1bytes
}MEM_ALIGN_DEMO_T;
案例分析
(1) 利用Excel形象化内存对齐问题
闲言少叙,直接上图。建议读者遇到内存对齐问题时,可以利用Excel将数据的存储和解析过程形象化。下面对图示几个部分做具体的说明:
- 原始数据
原始数据,即MEM_ALIGN_DEMO_T结构体各成员的原始数据,包括A->E共5个成员,其类型和数据在下图中一目了然。例如A=0x0208,占2个字节,以此类推。 - 8字节对齐存储
8字节对齐存储,即MEM_ALIGN_DEMO_T结构体以8字节对齐形式存入内存后的实际数据。图示1->8表示同一字节的Bit位序列,1->32表示不同字节各自的内存地址序列。需要注意如下几点:
【1】黄色区域是8字节对齐时自动补齐占位的字节,并且其值是不确定的,之所以图示中全部为零,是因为提前将MEM_ALIGN_DEMO_T结构体内存全部置0了。读者可以使用下文中的C程序进行验证,如果不将结构体内存全部置0,每次执行时,黄色区域的值是不一样的。
【2】8字节对齐,可以简单的理解成以8个字节为一个基本单元进行存储。比如,以图示1->8和9->16这两个基本元为例进行说明。A=0x0208占2字节,直接存入1->2两个字节即可;B=0x001C占2字节,接着A后面存入3->4两个字节即可;当继续存储C=0x00062F8100002A00的时候,第一个基本单元(1->8字节)只剩下4个字节了,不能存储占8个字节的C,所以C要放到下一个基本单元(9->16)存储,而第一个基本单元中的后4个字节(5->8)只起到占位补齐的作用。同理E后面的7个字节(25->32)也是占位补齐。
【3】细心的读者会发现,为什么存入内存后的字节是反的。比如说A=0x0208,存入内存后,变成了0x0802。这就涉及到大端模式和小端模式的问题,不是本文讨论的重点。大端模式指数据的高字节保存在内存的低地址,小端模式指数据的高字节保存在内存的高地址,笔者的系统是小端模式。 - 4字节对齐解析
4字节对齐解析,即以4字节对齐方式解析8字节对齐存储的结构体数据,部分数据就会面目全非。其中橙色部分的数据就会被舍弃,具体解析结果见解析数据部分。 - 解析数据
解析数据,即以4字节对齐方式解析之后的MEM_ALIGN_DEMO_T结构体各成员的实际数据。A和B未受影响,正好共同占用了4个字节,而解析C数据时会将补齐占位的5->8字节也当成C的数据部分,于是C、D、E数据都会发生错误,分别变成了0x00002A0000000000、0x000029E300062F81、0x81。
(2) 简单的C程序验证内存对齐问题
笔者做了一个简单的c程序mem_align.c,该程序实现MEM_ALIGN_DEMO_T结构体成员的value、addr、size的打印,并且以16进制输出实际存储在内存中的结构体数据,读者可以在此基础上进行验证和学习。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct mem_align_demo_t
{
unsigned short A; // 2bytes
unsigned short B; // 2bytes
unsigned long C; // 8bytes
unsigned long D; // 8bytes
unsigned char E; // 1bytes
}MEM_ALIGN_DEMO_T;
int main()
{
unsigned char *buffer = NULL;
unsigned int i = 0;
/** Define and print struct */
MEM_ALIGN_DEMO_T t;
memset(&t, 0, sizeof(t));
t.A = 0x0208;
t.B = 0x001c;
t.C = 0x00062f8100002a00;
t.D = 0x00062f81000029e3;
t.E = 0x02;
printf("=================================================================\n");
printf("\tvalue\t\t\taddr\t\t\tsize\n");
printf("t.A\t%04x\t\t\t%p\t\t%ld\n", t.A, &(t.A), sizeof(t.A));
printf("t.B\t%04x\t\t\t%p\t\t%ld\n", t.B, &(t.B), sizeof(t.B));
printf("t.C\t%016lx\t%p\t\t%ld\n", t.C, &(t.C), sizeof(t.C));
printf("t.D\t%016lx\t%p\t\t%ld\n", t.D, &(t.D), sizeof(t.D));
printf("t.E\t%02x\t\t\t%p\t\t%ld\n", t.E, &(t.E), sizeof(t.E));
printf("t\t--\t\t\t%p\t\t%ld\n", &t, sizeof(t));
printf("=================================================================\n");
/** Output actual stored struct content in hex */
buffer = (unsigned char *)malloc(sizeof(t));
memcpy(buffer, (unsigned char *)(&t), sizeof(t));
printf("Actual Struct Content:\n");
for (i=0; i<sizeof(t); i++)
{
printf("%02x", *(buffer + i));
}
printf("\n");
printf("=================================================================\n");
free(buffer);
return 1;
}
创建mem_align.c文件,将代码复制下来保存(笔者系统ubuntu18.04.3-x64,gcc版本7.5.0)。
编译:gcc -Wall -g mem_align.c -o mem_align
执行:./mem_align
输出结果如下,注意观察各成员的内存地址addr的变化,请读者自行和上述Excel表格对应分析,不做赘述。另外,整个结构体的大小为32字节,而不是2+2+8+8+1=21个字节,自然是因为8字节对齐时补齐占位造成的。
root@ubuntu:/home/share/study# gcc -Wall -g mem_align.c -o mem_align
root@ubuntu:/home/share/study# ./mem_align
=================================================================
value addr size
t.A 0208 0x7ffde716fac0 2
t.B 001c 0x7ffde716fac2 2
t.C 00062f8100002a00 0x7ffde716fac8 8
t.D 00062f81000029e3 0x7ffde716fad0 8
t.E 02 0x7ffde716fad8 1
t -- 0x7ffde716fac0 32
=================================================================
Actual Struct Content:
08021c0000000000002a0000812f0600e3290000812f06000200000000000000
=================================================================
root@ubuntu:/home/share/study#
总结说明
- 网上有很多关于内存对齐问题的博客,本文仅提供一个实际案例供读者参考学习。另外,强烈推荐使用Excel表格的方式去分析内存对齐问题。
- 笔者小白一枚,此文档仅用于学习记录和交流,如能帮助到读者倍感荣幸。