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。
    利用Excel形象化内存对齐问题

(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# 

总结说明

  1. 网上有很多关于内存对齐问题的博客,本文仅提供一个实际案例供读者参考学习。另外,强烈推荐使用Excel表格的方式去分析内存对齐问题。
  2. 笔者小白一枚,此文档仅用于学习记录和交流,如能帮助到读者倍感荣幸。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值