一:问题现象
在一个跨线程数据处理消息的时候出现了以下内存错位现象,在结构体指针引用的时候出现了成员数据异常
1.【数据源】线程A消息里面赋值的数据
//字节流
message.data[0] = (unsigned char)model_brake_disable_type_read();
message.data[1] = (unsigned char)(temp & 0xff);
message.data[2] = (unsigned char)((temp >> 8) & 0xff);
2.【目标结构体】线程B知道线程A该条消息的结构体,定义了专门的结构体来套字节流中的数据
typedef struct //目标结构体
{unsigned char break_disable_type;unsigned short break_disable_time;
}break_disable_info_t;
3.【问题出现】线程B通过直接打印和结构体成员引用的方式分别输出内容,发现结构体第二个成员输出内容异常❌
//套结构体体指针,以成员的形式引用data内容
break_disable_info_t *disableFrame = (break_disable_info_t *)message->data;printf("[pad] type%d\r\n", message->data[0]); //输出1,正确
printf("[pad] time%d\r\n", (*((short*)(message->data+1)))); //输出60,正确printf("[pad] type%d\r\n", disableFrame->break_disable_type); //输出1,正确
printf("[pad] time%d\r\n", disableFrame->break_disable_time); //输出0,错误
4.【反复调试】卡这里一直想不通,语法各方面都不存在问题,一直调试打印成员变量地址,结构体和成员大小发现了异常地方
发现两个结构体成员并不连续,中间出现了一个空位
进一步调试发现原本3个字节大小的结构体,在内存中占用了四个字节的位置,这正是导致输出数据异常的原因,多出的一个字节导致数据引用出现了错位
printf("[pad] p:%p, size:%d\r\n", disableFrame_p, sizeof(break_disable_info_t_));
二:原因解析
起初反复检查,查看之前代码也是这样处理的,没有任何问题,反复调试差点放弃打算换一种写法放过这个问题,最终还是比较犟,经过上面一顿懵逼和调试后,怀疑可能是编译器内存优化,找了一下资料得到一下结论:
在C语言中,struct
的大小通常取决于其成员的类型、数量以及可能的内存对齐(padding)。当你定义了如下的结构体:
typedef struct
{ char break_disable_type; unsigned short break_disable_time;
} break_disable_info_t_;
char
类型通常占用1个字节,而unsigned short
类型在大多数现代系统上占用2个字节。但是,由于数据对齐的需要,编译器可能会在char
成员后面插入填充字节(padding),以确保unsigned short
从适当的地址开始。
数据对齐是许多系统为了提高访问性能而采取的一种策略。对于某些处理器,从一个对齐的地址读取或写入数据比从一个未对齐的地址更快。此外,一些处理器甚至无法从未对齐的地址访问数据,或者这样做会导致硬件异常。
在你的例子中,char
后面可能有一个填充字节,以确保unsigned short
从2的倍数地址开始(这取决于编译器的具体实现和平台的对齐要求)。因此,整个结构体的大小可能是:
char
:1字节- 填充(padding):1字节(为了对齐
unsigned short
) unsigned short
:2字节
总共 4 字节。
你可以通过sizeof
运算符来检查结构体的大小,如下所示:
#include <stdio.h> typedef struct
{ char break_disable_type; unsigned short break_disable_time;
} break_disable_info_t_; int main() { printf("Size of break_disable_info_t_: %zu bytes\n", sizeof(break_disable_info_t_)); return 0;
}
如果你在输出中看到4
,那么说明编译器确实在char
和unsigned short
之间插入了一个填充字节。如果你想要避免这种填充(在某些情况下,比如内存使用非常关键时),你可以使用特定的编译器指令或属性来控制对齐,但这通常不是推荐的做法,除非你有明确的理由。