stm32 串口发送多字节数据(结构体版本)

本文介绍了一段实用的C代码,展示了如何通过定义结构体shuju处理连续数据传输,通过帧头和帧尾确保数据的有序发送。通过shuju_1结构实例和shujucunchu、send_data_函数,解决了长数据分批发送的问题。

话不多说先上代码

typedef struct shuju
{
  u8 sj0;   //帧头
  u8 sj1;
  u8 sj2;
  u8 sj3;
  u8 sj4;
  u8 sj5;
  u8 sj6;
  u8 sj7;
  u8 sj8;
  u8 sj9;    //帧尾
}shuju;

//实际使用
shuju shuju_1;

void shujucunchu(shuju *shuju_1)
{
  shuju_1->sj0 = 0xAA;  //帧头数据
  shuju_1->sj1 = 0xFD;
  shuju_1->sj2 = 0x02;
  shuju_1->sj3 = 0x00;
  shuju_1->sj4 = 0xDF;
  shuju_1->sj5 = 0xA5;
  shuju_1->sj6 = 0xA6;
  shuju_1->sj7 = 0xA7;
  shuju_1->sj8 = 0XA8;
  shuju_1->sj9 = 0xBB;  //帧尾数据
}

void send_data_(shuju*shuju_1,u8 len)
{
  static u8 date=0,i=0;
  for(i=0;i<len;i++) //使用sizeof计算结构体
  {
    date = *(((u8 *)&shuju_1->sj0)+i); //从帧头开始 然后依次向下指向
    USART_SendData(USARTx,date); //发送一个字节
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)!= SET);
  }
}

 

这个代码主要是解决了一个数据长而又无法一次性发送的问题

不懂就问,非常实用!

//主函数调用
shujucunchu(&shuju_1); //赋值
printf("sizeof:%d\r\n",sizeof(shuju_1)); //sizeof:10
send_data_(&shuju_1,sizeof(shuju_1)); //发送一帧数据

 

<think>我们讨论的是结构体数组地址不连续的问题。在结构体数组中,每个元素(结构体实例)的地址应该是连续的,即相邻两个结构体实例的地址相差等于结构体的大小。但有时由于内存对齐或编译器优化,可能会出现实际分配的地址不连续的情况。不过,在标准情况下,编译器会保证结构体数组中的元素是连续存放的。如果出现不连续,可能是由于一些特定的编译器行为、内存对齐要求或自定义的内存分配方式导致的。 但是,根据用户引用的内容[^1]:结构体内存对齐受模型影响,默认按2字节对齐(16位总线)。例如: ```c struct example { char c; // 偏移0 int i; // 偏移2(跳过1字节填充) char d; // 偏移4 }; // 总大小6字节 ``` 这里,结构体内部有填充,但结构体数组中的元素仍然是连续的。例如,定义一个数组`struct example arr[2]`,那么第二个元素`arr[1]`的起始地址应该是第一个元素`arr[0]`的起始地址加上结构体的大小(6字节)。因此,数组内部的元素是连续的。 但是,如果用户观察到结构体数组的地址不连续,可能是指数组中相邻两个结构体元素之间出现了额外的空隙?这通常不会发生,因为数组在内存中是连续分配的。除非: 1. 结构体有特殊的对齐要求(通过`alignas`指定或编译器默认对齐大于结构体大小),导致结构体数组分配时每个元素之间填充额外的字节以满足对齐。 2. 用户错误地将多个单独分配的结构体对象视为数组(例如,通过多个`malloc`分配,然后试图用指针算术访问它们,但这些分配的内存块不一定连续)。 ### 问题分析 #### 1. **结构体数组地址不连续的可能原因** - **结构体对齐要求大于其大小**:如果结构体使用了`alignas`指定了较大的对齐值(例如8字节对齐),而结构体实际大小不是该对齐值的整数倍,那么编译器在分配数组时会在每个元素后填充额外的字节,以保证下一个元素的对齐。例如: ```c struct alignas(8) example { char c; int i; char d; }; // 假设实际大小为6字节,但由于8字节对齐,数组每个元素会占用8字节 ``` 这样,数组元素之间就会有2字节的填充,导致地址间隔为8字节而不是6字节。 - **编译器默认对齐设置**:某些编译器(或编译选项)可能会设置较大的默认对齐,导致结构体数组元素之间存在填充。 - **单独分配的结构体对象**:如果数组元素是通过多次动态分配(如多次调用`malloc`)得到的,这些结构体对象在堆上的地址自然不连续。 - **自定义内存分配器**:如果使用了自定义内存分配器,可能有意在分配的内存块之间加入空隙(例如用于内存调试)。 #### 2. **影响** - 如果错误地假设结构体数组元素之间紧密连续(没有填充),通过指针算术访问下一个元素就会出错(可能访问到填充区域或越界)。 - 序列化/反序列化时,直接将整个数组写入文件或网络会包含填充字节,可能造成数据不一致。 #### 3. **解决方案** - **确保结构体紧密排列**:使用`#pragma pack`或`__attribute__((packed))`取消结构体内部填充,同时注意这样可能影响性能(未对齐访问)。 但注意:取消结构体内部填充并不能保证结构体数组元素之间没有额外的对齐填充。因为数组元素的对齐要求可能仍然存在(即结构体自身的对齐要求可能仍然较大)。 - **调整结构体对齐属性**:可以显式设置结构体的对齐方式为1字节,这样数组元素之间就不会有额外的填充: ```c struct __attribute__((packed, aligned(1))) example { char c; int i; char d; }; ``` 或者使用C11的`_Alignas`: ```c #include <stdalign.h> struct example { char c; int i; char d; }; // 然后定义数组时,可以强制元素按1字节对齐?但这样不可行。 // 实际上,我们可以将整个数组按1字节对齐,或者设置结构体对齐为1。 struct example arr[10] __attribute__((aligned(1))); ``` 但更好的方法是设置结构体本身的对齐为1: ```c struct __attribute__((aligned(1))) example { ... }; ``` 注意:`aligned(1)`表示最小对齐(1字节对齐),这样数组元素之间就不会有填充。但这样可能会导致未对齐的内存访问,降低性能或在某些架构上引发错误。 - **计算实际偏移**:在访问数组元素时,使用`sizeof`运算符计算下一个元素的地址,而不是假设相邻元素之间的偏移为结构体的大小(因为`sizeof`已经包含了编译器在数组元素之间可能添加的填充)。例如: ```c struct example *p = &arr[0]; struct example *next = (struct example *)((char *)p + sizeof(struct example)); // 正确 // 而不是 p + 1,因为指针运算会自动考虑结构体大小(包括数组元素之间的填充) ``` 实际上,用指针运算`p+1`也是正确的,因为编译器知道结构体的大小(包括末尾可能为了数组对齐而添加的填充)。所以,使用指针运算就可以避免手动计算。 - **避免单独分配**:如果需要连续存储,应该一次性分配整个数组,而不是单独分配每个元素。 ### 总结 标准情况下,结构体数组中的元素是连续的。如果出现不连续,可能是由于结构体有较大的对齐要求(导致数组元素间有填充),或者用户误将多个独立分配的结构体视为数组。 解决办法: 1. 调整结构体的对齐方式(如设置为1字节对齐)以消除数组元素间的填充(但注意性能影响)。 2. 使用数组分配方式(一次性分配)确保连续。 3. 使用指针运算(而不是手动地址计算)来访问数组元素,这样编译器会正确处理元素间隔。 ### 相关问题 1. 如何检测结构体数组元素之间的填充? 2. 结构体对齐设置对程序性能有哪些具体影响? 3. 在序列化结构体数组时,如何避免填充字节的影响? 4. 跨平台编程时,如何处理不同编译器对结构体数组内存布局的差异? </think>### 结构体数组地址不连续的原因及解决方案 #### 一、地址不连续的根本原因 在结构体数组中,相邻结构体元素地址不连续主要由以下机制导致: 1. **结构体尾部填充(Trailing Padding)** 编译器会在结构体末尾添加填充字节,使结构体总大小为**对齐要求的整数倍**(如2/4/8字节)。引用[1]中示例: ```c struct example { char c; // 偏移0 int i; // 偏移2(填充1字节) char d; // 偏移4 }; // 总大小=6字节(按2字节对齐)[^1] ``` 若数组元素大小为6字节,但系统要求8字节对齐,则元素间会自动插入2字节填充。 2. **数组全局对齐要求** 即使单个结构体无尾部填充,编译器为保证数组首地址满足架构对齐要求(如SSE指令需16字节对齐),也可能在元素间插入间隙[^1]。 3. **编译器优化指令** 使用`#pragma pack(n)`仅控制**结构体内部**对齐,无法消除数组层面的填充: ```c #pragma pack(1) // 结构体内部无填充 struct Packet { uint8_t type; uint32_t data; // 结构体大小=5字节 }; #pragma pack() struct Packet arr[2]; // 若系统要求4字节对齐,元素间可能有3字节填充 ``` #### 二、核心影响 1. **内存计算错误** `sizeof(struct) * N` ≠ 实际数组占用空间,导致内存分配不足或越界。 ```c struct Packet *buf = malloc(2 * sizeof(struct Packet)); // 实际可能需要更多空间 ``` 2. **指针运算失效** 通过地址偏移访问下一个元素会跳转到错误位置: ```c void *next = (char*)&arr[0] + sizeof(struct Packet); // 可能指向填充区而非arr[1] ``` 3. **数据序列化错误** 直接`memcpy`整个数组会包含填充字节,破坏协议一致性: ```c send(socket, arr, sizeof(arr), 0); // 发送了无效填充字节[^2] ``` #### 三、实用解决方案 ##### 方法1:强制编译器紧凑布局(推荐) 同时控制结构体内外对齐: ```c #pragma pack(push, 1) // 强制1字节对齐 struct SensorData { uint8_t id; float value; // 结构体大小=5字节 }; #pragma pack(pop) // 显式指定数组对齐方式 __attribute__((aligned(1))) struct SensorData arr[100]; ``` > ✅ 确保数组元素间无填充 > ⚠️ 注意:过度紧凑可能降低CPU访问性能 ##### 方法2:手动计算元素偏移 通过地址运算精确控制: ```c size_t element_size = sizeof(struct Packet); // 包含内部填充的结构体大小 size_t array_size = element_size * N; // 动态分配时显式对齐 struct Packet *arr = aligned_alloc(64, array_size); // 按64字节对齐分配 // 访问第i个元素 struct Packet *p = (struct Packet*)((uint8_t*)arr + i * element_size); ``` ##### 方法3:自定义序列化处理 网络传输/文件存储时忽略填充字节: ```c // 发送单个元素(不含填充) void send_packet(int socket, struct Packet *p) { uint8_t raw_data[5]; // 实际数据大小 memcpy(raw_data, &p->type, 1); memcpy(raw_data+1, &p->data, 4); send(socket, raw_data, 5, 0); } // 接收端重建结构体 void recv_packet(int socket, struct Packet *p) { uint8_t raw_data[5]; recv(socket, raw_data, 5, 0); p->type = raw_data[0]; memcpy(&p->data, raw_data+1, 4); } ``` #### 四、诊断工具 检测实际内存布局: ```c printf("结构体大小: %zu\n", sizeof(struct example)); printf("地址差: %td\n", (char*)&arr[1] - (char*)&arr[0]); // 输出示例: // 结构体大小: 6 // 地址差: 8 <- 存在2字节填充 ``` ### 相关问题 1. 如何检测结构体数组元素间的填充字节数量? 2. `#pragma pack`在不同编译器(GCC/MSVC/Clang)中的行为差异有哪些? 3. 结构体尾部填充在跨平台数据传输中会引发哪些兼容性问题? 4. C++中`alignas`关键字如何解决结构体数组对齐问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燃烧的奶茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值