对几组sizeof信息的分析

博客针对C++新手对对象或变量sizeof信息的疑惑,列举多组范例解答。介绍Win32平台指针长度,说明编译器对结构体或类成员变量自动对齐以提高效率,不推荐强制对齐。通过不同范例展示数组、结构体、类在sizeof运算中的情况及C++语言特征对其的影响。
对于很多C++新手而言,对象或变量的sizeof信息总是让人捉摸不透,以下程序列举了几组典型的sizeof信息,希望能解答大家在使用sizeof时的疑问。
在列举这几个例子前需要说明以下几点:
1
、在Win32平台上,指针长度都是4字节,char*int*double*如此,vbptr(virtual base table pointer)、vfptr(virtual function table pointer)也是如此;
2
、对于结构体(或类),编译器会自动进行成员变量的对齐,以提高运算效率。自然对齐(natural alignment)也称默认对齐方式是按结构体的成员中size最大的成员对齐的,强制指定大于自然对齐大小的对齐方式是不起作用的(Specifying a packing level larger than the natural alignment of a type does not change the type alignment.)
3
、不推荐强制对齐,大量使用强制对齐会严重影响处理器的处理效率。

范例1(一个简单的C语言的例子)
void
 f(int arr[])
{

    cout << "sizeof(arr) = " << sizeof(arr) << endl; //当被作为参数进行传递时,数组失去了其大小信息
}

void
 main()
{

    char
 szBuf[] = "abc";
    cout << "sizeof(szBuf) = " << sizeof(szBuf) << endl; //输出数组占用空间大小

    char
* pszBuf = szBuf;
    cout << "sizeof(pszBuf) = " << sizeof(pszBuf) << endl; //输出的是指针的大小

    int
 iarr[3]; iarr;
    cout << "sizeof(iarr) = " << sizeof(iarr) << endl; //输出数组占用空间大小
    f(iarr);

    int
* piarr = iarr;
    cout << "sizeof(piarr) = " << sizeof(piarr) << endl; //输出指针的大小
}

范例2(一个涉及alignment的例子)
struct
 DATA1
{

    char
    c1; //偏移量0,累积size = 1
    char    c2; //偏移量1,累积size = 1 + 1 = 2
    short    si; //偏移量2,累积size = 2 + 2
};

struct
 DATA2
{

    char
    c1; //偏移量0,累积size = 1
    short    si; //偏移量1 + (1),累积size = 1 + (1) + 2 = 4
    char    c2; //偏移量4,累积size = 4 + 1 = 5,但按最大长度sizeof(short) = 2对齐,故最后取6
};

struct
 DATA3
{

    char
    c1; //偏移量0,累积size = 1
    double    d; //偏移量1 + (7),累积size = 1 + (7) + 8 = 16
    char    c2; //偏移量16,累积size = 16 + 1 = 17,但按最大长度sizeof(double) = 8对齐,故最后取24
};

#pragma pack(push,1) //强制1字节对齐
struct DATA4
{

    char
    c1; //偏移量0,累积size = 1
    double    d; //偏移量1,累积size = 1 + 8 = 9
    char    c2; //偏移量9,累积size = 9 + 1 = 10
};
#pragma pack(pop) //恢复默认对齐方式

struct
 DATA5
{

    char
    c1;
    double
    d;
    char
    c2;
};


void
 main()
{

    cout << "sizeof(DATA1) = " << sizeof(DATA1) << endl;
    cout << "sizeof(DATA2) = " << sizeof(DATA2) << endl;
    cout << "sizeof(DATA3) = " << sizeof(DATA3) << endl;
    cout << "sizeof(DATA4) = " << sizeof(DATA4) << endl;
    cout << "sizeof(DATA5) = " << sizeof(DATA5) << endl;
}


范例3(C++语言特征对sizeof的影响)
class
 CA
{
};


class
 CB : public CA
{

public
:
    void
 func() {}
};


class
 CC : virtual public CA
{
};


class
 CD
{

    int
 k; //私有成员
public:
    CD() {k = -1;}
    void
 printk() { cout << "k = " << k << endl; }
};


class
 CE : public CD
{
};


class
 CF
{

    virtual
 void func() {}
};


void
 main()
{

    cout << "sizeof(CA) = " << sizeof(CA) << endl; //为了区分不包含任何成员的类的不同的元素,编译器会自动为类添加一个匿名元素
    cout << "sizeof(CB) = " << sizeof(CB) << endl; //与上面类似,编译器也为CB添加了一个匿名元素
    cout << "sizeof(CC) = " << sizeof(CC) << endl; //虚拟继承中vbptr(virtual base table pointer)占用4个字节

    cout << "sizeof(CD) = " << sizeof(CD) << endl;
    cout << "sizeof(CE) = " << sizeof(CE) << endl; //访问权限控制是在编译期间由编译器控制的,所以虽然不能访问CD类的成员k,这里仍然占用了sizeof(int)大小的空间
    //下面的代码进一步说明上述观点,由于在复杂的类层次结构中,当涉及到虚函数或者虚拟继承等时,有些信息是运行期动态生成的,故请勿效仿以下方法对对象进行修改
    CE e;
    e.printk();
    memset(&e, 0, sizeof(CE));
    e.printk(); //从这里可以看出,上面的memset操作修改了CD类的私有成员k

    cout << "sizeof(CF) = " << sizeof(CF) << endl; //虚函数表指针占用4个字节
}
<think> 根据之前的问题,用户提供的代码是STM32 HAL库的UART接收中断回调函数,该函数用于处理串口接收数据帧(以0x6B开头,0x7F结尾)。用户问为什么错了,我分析了错误原因并给出了修正代码。现在用户要求“给我几组调试程序”,我理解为用户需要几组测试数据来调试修改后的程序。 因此,我将提供几组用于测试的串口数据帧,这些数据帧覆盖各种边界情况和异常情况,以验证程序的健壮性。同时,我会说明每组测试数据的目的和预期结果。 注意:测试时,需要将数据按照字节逐个发送到串口,模拟接收中断。 测试数据组: 1. 正常帧(短帧): - 发送:0x6B, 0x01, 0x02, 0x7F - 预期:正确接收,触发发送,发送的数据为{0x6B,0x01,0x02,0x7F},长度为4。 2. 正常帧(长帧,但不超过缓冲区): - 发送:0x6B, [128个字节的数据,但最后一个字节为0x7F](实际数据长度为130,包括帧头和帧尾,但缓冲区大小为128,所以会失败?) - 注意:修正后的代码中,缓冲区大小定义为128(示例)。如果数据索引达到128(即已经存储了128个字节),再接收新字节会进入缓冲区满处理,放弃帧。因此需要调整测试数据长度。 - 修改:发送一个长度为127个数据(加上帧头和帧尾共129字节)的帧,因为缓冲区大小为128,所以最后一个字节(第129个)会触发缓冲区满,从而放弃帧。但这样测试并不完全符合正常长帧。 - 调整测试:我们发送一个长度等于缓冲区大小的帧(128字节),包括帧头和帧尾。但帧头已经占一个字节,帧尾占一个字节,所以中间数据为126字节。即: 0x6B, [126个任意字节], 0x7F -> 总长度128。 - 预期:能够完整接收并发送。 3. 超长帧(超过缓冲区): - 发送:0x6B, [128个字节], 0x7F (总长度为130,超过128) - 过程:在接收到第128个字节(即帧头后的第127个字节)时,此时索引为128(因为帧头已经存了,然后127个数据字节,索引变成128,等于缓冲区大小),当再接收下一个字节时(此时应该是帧尾0x7F),会进入RECEIVING状态的else分支(因为data_index>=sizeof(Data)),然后状态重置为IDLE,索引重置。然后处理帧尾时,由于状态已经是IDLE,所以帧尾会被当作普通数据忽略(除非帧尾单独出现且前面有帧头,但此时状态已重置)。 - 注意:在超长帧中,当索引达到128时,状态会重置为IDLE,然后下一个字节(0x7F)会被当作新的帧的起始?不会,因为状态已经是IDLE,而0x7F不是起始字节。所以这整个帧会被丢弃。 - 预期:不会触发发送。 4. 帧头后紧接帧头(协议不允许,但测试鲁棒性): - 发送:0x6B, 0x6B, 0x7F - 预期:第一个0x6B进入接收状态,第二个0x6B被当作数据存储(因为状态为RECEIVING),然后0x7F作为帧尾,触发发送。发送的数据为{0x6B,0x6B,0x7F}。 5. 帧头后未收到帧尾,然后超长(导致缓冲区满): - 发送:0x6B, [连续发送128个字节(不包括帧尾)](即帧头后128个字节,此时索引从0(存帧头)到1,2,...,128,当存到第128个字节时,索引为128(缓冲区满),然后状态被重置为IDLE,索引重置。然后再发送一个0x7F,此时状态为IDLE,所以0x7F不会被当作帧尾。 - 预期:不会触发发送。 6. 未收到帧头时收到帧尾: - 发送:0x7F, 0x6B, 0x01, 0x7F - 预期:第一个0x7F被忽略(状态IDLE),然后0x6B作为帧头开始接收,0x01作为数据,0x7F作为帧尾,触发发送。发送数据为{0x6B,0x01,0x7F}。 7. 空帧(只有帧头和帧尾): - 发送:0x6B, 0x7F - 预期:正常接收并发送,数据为{0x6B,0x7F}。 8. 帧头后,在收到帧尾前又收到帧头(即两个帧头): - 发送:0x6B, 0x01, 0x6B, 0x02, 0x7F - 预期:第一个0x6B进入接收状态,存储0x6B,0x01,然后收到0x6B(此时状态为RECEIVING,所以当作普通数据存储),然后0x02,0x7F。最后发送的数据为{0x6B,0x01,0x6B,0x02,0x7F}。 9. 帧头后,缓冲区未满,但未收到帧尾,然后收到下一个帧头(即前一帧未结束,下一帧开始): - 发送:0x6B, 0x01, 0x6B, 0x02, 0x7F - 同上,实际上会被当作一帧处理。因为第二个0x6B并不会中断当前帧。 10. 帧尾后紧接着下一帧: - 发送:0x6B, 0x01, 0x7F, 0x6B, 0x02, 0x7F - 预期:第一帧接收完成后,状态重置为IDLE,然后下一个0x6B作为新帧的帧头,第二帧正常接收。会触发两次发送。 11. 干扰数据:在帧外出现的数据(不在帧头和帧尾之间): - 发送:0x55, 0xAA, 0x6B, 0x01, 0x7F, 0xAA, 0x55 - 预期:只有0x6B,0x01,0x7F被当作一帧,触发发送。前后的0x55,0xAA和0xAA,0x55被忽略。 12. 快速连续发送多帧: - 发送:0x6B,0x01,0x7F,0x6B,0x02,0x7F,0x6B,0x03,0x7F - 预期:连续触发三次发送,分别发送三帧。 这些测试用例覆盖了常见情况。用户可根据这些用例调试程序。 注意:在测试时,需要确保串口接收中断能够及时处理每个字节(即发送速度不要超过处理能力)。另外,由于主循环中发送是阻塞的,如果发送一帧的时间过长,可能会丢失后续接收的数据。因此,在实际应用中,建议使用DMA或非阻塞方式发送。 下面给出一个简单的测试方法(伪代码): - 在PC上使用串口调试助手,以字节形式发送上述测试数据。 - 在STM32端,将接收到的帧通过串口发送回PC,通过调试助手查看是否与预期一致。 如果用户需要更详细的测试程序(如C语言模拟测试),请用户进一步说明。 §§相关问题§§: 1. 在嵌入式系统中,如何设计一个高效的串口数据帧解析状态机? 2. 使用DMA进行串口接收和发送有哪些优势?如何实现? 3. 在多任务系统中,如何安全地在中断和任务之间传递数据? 4. 如何处理串口通信中的错误(如奇偶校验错误、噪声错误)? </think>以下是几组用于调试和验证串口通信程序的测试用例,覆盖了各种边界条件和异常场景。这些测试用例基于您提供的代码逻辑(以 `0x6B` 开头、`0x7F` 结尾的数据帧),使用 Python 编写,可模拟串口数据输入并验证程序行为。 --- ### 测试用例 1:正常完整帧 **测试目的**:验证程序能正确处理标准数据帧 **输入数据**:`[0x6B, 0x01, 0x02, 0x03, 0x7F]` **预期输出**:发送缓冲区 `Data = [0x6B, 0x01, 0x02, 0x03, 0x7F]`,长度=5 ```python def test_normal_frame(): # 模拟输入数据流 input_data = [0x6B, 0x01, 0x02, 0x03, 0x7F] # 全局状态(模拟STM32全局变量) RX_Data = 0 data = 0 i = 0 Data = [0] * 128 # 128字节缓冲区 # 模拟HAL中断回调 for byte in input_data: RX_Data = byte # 以下是原始代码逻辑 if RX_Data == 0x6B: data = 1 i = 0 Data[i] = RX_Data i += 1 elif RX_Data == 0x7F and data == 1: Data[i] = RX_Data i += 1 print(f"[正常帧] 发送数据: {Data[:i]}, 长度={i}") data = 0 i = 0 elif data == 1: if i < len(Data): Data[i] = RX_Data i += 1 else: data = 0 i = 0 test_normal_frame() ``` --- ### 测试用例 2:缓冲区溢出测试 **测试目的**:验证缓冲区满时的安全处理 **输入数据**:`[0x6B] + [0xAA]*128 + [0x7F]`(共130字节) **预期输出**: - 当 `i=128` 时重置状态 - 不触发发送操作 ```python def test_buffer_overflow(): input_data = [0x6B] + [0xAA]*128 + [0x7F] # 129字节 RX_Data = 0 data = 0 i = 0 Data = [0] * 128 overflow_occurred = False for idx, byte in enumerate(input_data): RX_Data = byte if RX_Data == 0x6B: # ...(同测试用例1) elif RX_Data == 0x7F and data == 1: # ...(同测试用例1) elif data == 1: if i < len(Data): Data[i] = RX_Data i += 1 else: print(f"[溢出] 在位置 {idx} 检测到缓冲区满") overflow_occurred = True data = 0 i = 0 assert overflow_occurred, "缓冲区溢出未正确处理!" print("测试通过:溢出状态已重置") test_buffer_overflow() ``` --- ### 测试用例 3:多帧连续传输测试 **测试目的**:验证连续帧处理的稳定性 **输入数据**:`[0x6B, 0x11, 0x7F, 0x6B, 0x22, 0x7F]`(两帧连续) **预期输出**: 1. 第一帧发送 `[0x6B, 0x11, 0x7F]` 2. 第二帧发送 `[0x6B, 0x22, 0x7F]` ```python def test_multi_frame(): input_data = [0x6B, 0x11, 0x7F, 0x6B, 0x22, 0x7F] frames_received = [] # ...(初始化代码同前) for byte in input_data: RX_Data = byte if RX_Data == 0x6B: # ...(同前) elif RX_Data == 0x7F and data == 1: # 记录发送的帧 frames_received.append(Data[:i+1]) # ...(重置状态) # ...(其他逻辑) print(f"收到 {len(frames_received)} 帧:") for i, frame in enumerate(frames_received): print(f"帧{i+1}: {frame}") assert len(frames_received) == 2, "应接收2帧数据" assert frames_received[0] == [0x6B, 0x11, 0x7F] assert frames_received[1] == [0x6B, 0x22, 0x7F] test_multi_frame() ``` --- ### 测试用例 4:异常数据测试 **测试目的**:验证对非法数据的鲁棒性 **输入数据**:`[0x7F, 0x6B, 0x7F, 0x6B, 0x55, 0x6B, 0x7F]` **预期输出**: - 仅接收有效帧 `[0x6B, 0x7F]` 和 `[0x6B, 0x55, 0x6B, 0x7F]` - 忽略孤立的 `0x7F` ```python def test_invalid_data(): input_data = [0x7F, 0x6B, 0x7F, 0x6B, 0x55, 0x6B, 0x7F] valid_frames = [] # ...(初始化代码) for byte in input_data: # ...(处理逻辑) if RX_Data == 0x7F and data == 1: # 记录有效帧 valid_frames.append(Data[:i+1]) # ...(重置) print("有效帧列表:") for frame in valid_frames: print(frame) assert [0x6B, 0x7F] in valid_frames, "缺少短帧" assert [0x6B, 0x55, 0x6B, 0x7F] in valid_frames, "缺少长帧" test_invalid_data() ``` --- ### 测试建议流程 1. **硬件测试**: ```c // 在主循环添加调试输出 printf("状态: %d, 索引: %d, 最新字节: 0x%02X\n", data, i, RX_Data); ``` 2. **逻辑分析仪**:监控USART的TX/RX引脚,验证时序: - 帧起始后RX是否持续接收 - 帧结束时TX是否触发 3. **边界值测试**: ```python # 测试零长度帧(只有头尾) test_frame([0x6B, 0x7F]) # 测试缓冲区临界点(127字节数据+头尾) test_frame([0x6B] + [0x00]*126 + [0x7F]) ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值