c语言结构体属性之__attribute__((packed))
0. 引出
事情是这样的,最近在做代码移植时见到了这样一段代码
typedef struct {
uint16_t len;
uint8_t data[PAIRING_MAX_LEN];
} pairing_rx_buffer_t;
static pairing_rx_buffer_t pairing_rx_buffer = {};
typedef struct{
uint8_t c2[C2_BLEN];
uint8_t e3[E3_BLEN];
uint8_t seeds[SEEDS_BLEN];
uint8_t icloud_id[ICLOUD_IDENTIFIER_BLEN];
uint8_t s2[S2_BLEN];
} __attribute__((packed)) fmna_finalize_pairing_data_t;
typedef struct {
FMNA_Service_Opcode_t finalize_pairing_opcode;
fmna_finalize_pairing_data_t finalize_pairing_data;
} __attribute__((packed)) finalize_pairing_packet_t;
fmna_finalize_pairing_data_t m_fmna_finalize_pairing_data
memcpy(&m_fmna_finalize_pairing_data,
&(((finalize_pairing_packet_t *)pairing_rx_buffer.data)->finalize_pairing_data),
sizeof(m_fmna_finalize_pairing_data));
心里飘过一万个疑问? 这是什么操作,各种结构体包含结构体,各种类型转换,各种取地址符,还把一个uint8的数组的值copy到一个结构体里面?还有结构体为什么后面还加
**attribute**((packed))
这个鬼东东 fine c语言为什么这么难
1. 解释
把代码粘贴到chatxxx他是这样回答的
这段代码定义了三个结构体:
pairing_rx_buffer_t
、fmna_finalize_pairing_data_t
和finalize_pairing_packet_t
。其中,fmna_finalize_pairing_data_t
和finalize_pairing_packet_t
都使用了__attribute__((packed))
属性。
pairing_rx_buffer_t
结构体包含一个 uint16_t
类型的 len
成员和一个长度为 PAIRING_MAX_LEN
的 uint8_t
数组 data
成员。
fmna_finalize_pairing_data_t
结构体包含五个成员,分别是长度为 C2_BLEN
的 c2
数组,长度为 E3_BLEN
的 e3
数组,长度为 SEEDS_BLEN
的 seeds
数组,长度为 ICLOUD_IDENTIFIER_BLEN
的 icloud_id
数组和长度为 S2_BLEN
的 s2
数组。此外,该结构体使用了 __attribute__((packed))
属性,表示该结构体的成员顺序紧密排列,没有使用任何填充字节。
finalize_pairing_packet_t
结构体包含两个成员:一个 FMNA_Service_Opcode_t
类型的 finalize_pairing_opcode
成员和一个 fmna_finalize_pairing_data_t
类型的 finalize_pairing_data
成员。同样地,该结构体使用了 __attribute__((packed))
属性,表示该结构体的成员顺序紧密排列,没有使用任何填充字节。
在代码中,首先声明并初始化了一个 pairing_rx_buffer_t
类型的变量 pairing_rx_buffer
。然后,声明了一个 fmna_finalize_pairing_data_t
类型的变量 m_fmna_finalize_pairing_data
。最后,使用 memcpy
函数将从 pairing_rx_buffer.data
中复制 finalize_pairing_data
成员的数据到 m_fmna_finalize_pairing_data
变量中,复制的长度为 sizeof(m_fmna_finalize_pairing_data)
。这个操作实际上是将 finalize_pairing_packet_t
结构体中的 finalize_pairing_data
成员赋值给 m_fmna_finalize_pairing_data
变量。由于 finalize_pairing_packet_t
结构体成员的顺序是紧密排列的,因此可以简单地使用指针和 memcpy
函数进行赋值。
结构体成员的顺序紧密排列,意味着成员之间没有填充字节。这就保证了结构体在内存中的布局是连续的,每个成员的地址是按照其顺序连续分布的。因此,可以使用指针和
memcpy
函数直接将结构体的内存块复制到另一个内存块中,从而实现赋值的操作。
如果结构体成员之间存在填充字节,那么结构体在内存中的布局就不再是连续的,每个成员的地址也不再是按照其顺序连续分布的。这就导致不能简单地使用指针和 memcpy
函数进行赋值,需要按照结构体成员的顺序逐个进行赋值操作。
需要注意的是,使用指针和 memcpy
函数进行赋值操作时,需要保证两个结构体的布局是相同的,即结构体成员的类型和顺序都相同。如果两个结构体的布局不同,那么就会导致数据传输或解析错误。因此,在使用指针和 memcpy
函数进行赋值操作时,需要谨慎考虑结构体的布局问题,并确保程序的正确性和可靠性。
2. 实验细究
那我懂了,它的意思是可以把一段缓存区的数据通过memcpy的方式直接给结构体的成员变量复制,因为加了__attribute__((packed))属性的结构体不会填充字节不会自动字节对齐,即每个成员变量在内存的布局是连续的,
同样的一段结构体加了__attribute__((packed))属性和没加__attribute__((packed))属性它的内存大小就非常不同
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
int a;
char b;
int c;
}s1;
typedef struct
{
int a;
char b;
int c;
}__attribute__((packed)) s2;
int main(void)
{
printf("the size of s1 is %ld\n", sizeof(s1));
printf("the size of s2 is %ld\n", sizeof(s2));
return 0;
}
那如果我想把一段缓存区的数据直接copy到结构体,赋值到对应的每位成员变量呢?可以看下面这段代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int a;
char b;
int c;
}S1;
typedef struct
{
int a;
char b;
int c;
}__attribute__((packed)) S2;
int main(void)
{
S1 s1;
S2 s2;
char buffer[9] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
memcpy(&s1, buffer, sizeof(s1));
memcpy(&s2, buffer, sizeof(s2));
printf("s1.a: %04x s1.b: %x s1.c: %04x\n", s1.a, s1.b, s1.c);
printf("s2.a: %04x s2.b: %x s2.c: %04x\n", s2.a, s2.b, s2.c);
printf("the size of s1 is %ld\n", sizeof(S1));
printf("the size of s2 is %ld\n", sizeof(S2));
return 0;
}
我们把代码可视化看一下
可以看到有了字节填充后赋值就变得很奇怪了不能一一对应上,加了之后就可以一一对应上了这就是__attribute__((packed))其中的一个使用场景
画一张图可能会更好理解一点
3. 结论
attribute((packed))会让结构体变得更加紧凑,这样我们就可以使用memcpy指针操作对结构体做一个初始化了,比如说我有一大段缓冲区的数据,我想直接把数据copy到结构体里面就可以采用这种方式,但要注意的是一定要数据的每个字节都要一一对应,不然会出现奇怪的效果,哎,这能也是c语言数据结构麻烦的表现,实现这种效果要搞这么多东西,python的话不是随便搞,我直接对list进行切割,或者用dataclass nameturple对数据进行操作就行啦 花里胡哨