对于下面的示例代码,方法1和2都可以从数组中提取数据。这两种方法的优缺点是什么?方法1看起来更干净,但必须小心处理字节序。还有什么?
#include <stdio.h>
#include <stdint.h>
uint8_t data[] = {0x8A, 0x02, 0x03, 0x04, 0x05};
#pragma pack(1)
typedef struct
{
uint8_t hex2:4;
uint8_t hex1:4;
uint16_t num1;
uint16_t num2;
} RandomStruct;
#pragma pack()
int main()
{
// Method 1
RandomStruct *struct1 = (RandomStruct *)data;
printf("%X\n", struct1->hex1);
printf("%X\n", struct1->hex2);
printf("%X\n", struct1->num1);
printf("%X\n", struct1->num2);
// Method 2
uint8_t hex1 = data[0] >> 4;
uint8_t hex2 = data[0] & 0x0F;
uint16_t num1 = data[1] | (data[2] << 8);
uint16_t num2 = data[3] | (data[4] << 8);
printf("%X\n", hex1);
printf("%X\n", hex2);
printf("%X\n", num1);
printf("%X\n", num2);
return 0;
}
字符串
1条答案
按热度按时间bzzcjhmw1#
方法1
问题1:
struct1->hex1
、struct1->hext2
、struct->num1
和struct1->num2
访问data
的内存,该内存具有有效类型uint8_t
(可能是字符类型),但它们访问的是具有结构类型的内存。其行为未定义,因为它不符合C 20186 6.5 7中的别名规则:对象的储存值只能由具有下列其中一种型别的左值运算式存取:...
(这里有一个复杂的问题,因为别名规则允许“在其成员中包含上述类型之一的聚合或联合类型”,但我不希望在声明位字段成员时名义上使用
uint8_t
来满足这一点。)可以通过将
RandomStruct *struct1 = (RandomStruct *)data;
更改为RandomStruct struct1;
、使用memcpy(&struct1, data, sizeof struct1);
并将后续出现的struct1->
更改为struct1.
来修复此问题。问题2:C标准未规定
hex2
和hex1
在用于容纳它们的存储单元中的位置顺序,根据C 2018 6.7.2.1 11:...一个单元内位字段的分配顺序(高阶到低阶或低阶到高阶)由实现定义...
请注意,位字段的分配顺序不是由C实现的字节序确定的。不管C实现是将整数类型的低有效位字节较早地放置在存储器中还是较晚地放置在存储器中,它都可以将结构的第一位字段放置在存储单元的低位中或存储单元的高位中。
这在C语言中是无法移植的(也就是说,只使用严格一致的代码);您必须确保对于您使用的每个C实现都以必要的顺序声明了成员。
问题3:该结构的两个位字段不一定只占用一个字节。C 2018 6.7.2.1第11期称:
实现可以分配任何大到足以容纳位字段的可寻址存储单元......
这是与打包结构不同的问题,因为用于位字段的存储单元将被视为用于位字段的字节,而不是通过打包消除的填充字节。
这在C语言中无法移植修复,但使用
_Static_assert(sizeof (RandomStruct) == 5, "Error, expected just five bytes in RandomStruct.");
可以检测到与所需布局的冲突。方法2
问题1:
data[1] | (data[2] << 8)
不能保证在每个C实现中都能工作,因为data[2]
将被提升为int
,而C标准允许int
为16位。如果data[2]
的高位被置位,则data[2] << 8
将产生大于或等于32,768的结果,这在16位int
中是无法表示的。然后发生溢出,C标准未定义该行为,根据C 2018 6.5.7 4:...如果
E1
具有带符号的类型和非负值,并且E1
× 2x 1 m24n1x在结果类型中可表示,则这是结果值;否则,行为未定义。这可以通过将
data[2]
转换为uint16_t
以及类似地将data[4]
转换为uint16_t
来修复。