我想使用 RGBA 值表示 32 位数字,使用联合生成该数字的值是否可移植?考虑这个 C 代码;
union pixel {
uint32_t value;
uint8_t RGBA[4];
};
Run Code Online (Sandbox Code Playgroud)
这编译得很好,并且我喜欢使用它而不是一堆函数。但这安全吗?
这称为“类型双关” ,并且由于字节顺序考虑,它不能直接移植。不过,除此之外,这样做就好了。C 标准并没有很好地表明这很好,但显然确实如此。阅读这些答案和来源:
\n此外,C18 草案N2176 ISO/IEC 9899:2017在“6.5.2.3 结构和联合成员”部分中规定,脚注 97 中如下:
\n\n\n\n
\n- 如果用于读取联合对象内容的成员与最后用于在对象中存储值的成员不同,则该值的对象表示形式的相应部分将被重新解释为新对象表示形式中的对象表示形式ntype 如 6.2.6 中所述(该过程有时称为 \xe2\x80\x9ctype 双关语\xe2\x80\x9d)。这可能是一个陷阱表示。
\n
在此屏幕截图中查看:
\n\n所以,有
\ntypedef union my_union_u\n{\n uint32_t value;\n /// A byte array large enough to hold the largest of any value in the union.\n uint8_t bytes[sizeof(uint32_t)];\n} my_union_t;\n
Run Code Online (Sandbox Code Playgroud)\n在 C++ 中,它作为 GNU gcc 扩展(但不作为 C++ 标准的一部分)value
工作。请参阅@Christoph 在他的回答中的解释:bytes
\n\nGNU 对标准 C++(和 C90)的扩展明确允许使用 union 进行类型双关。其他不支持 GNU 扩展的编译器也可能支持联合类型双关,但它不是基本语言标准的一部分。
\n
下载代码:您可以从我的 eRCaGuy_hello_world存储库下载并运行以下所有代码:“type_punning.c”。C和C++ 的 gcc 构建和运行命令可在文件最顶部的注释中找到。
\n因此,您可以执行以下操作来读取单个字节uint32_t value
:
技术 1:基于联合的类型双关(这是“类型双关”):
\n这就是“类型双关”的意思:将一种类型写入联合体,然后读出另一种类型,从而利用联合体进行类型“转换”。
\nmy_union_t u;\n\n// write to uint32_t value\nu.value = 1234;\n\n// read individual bytes from uint32_t value\nprintf("1st byte = 0x%02X\\n", (u.bytes)[0]);\nprintf("2nd byte = 0x%02X\\n", (u.bytes)[1]);\nprintf("3rd byte = 0x%02X\\n", (u.bytes)[2]);\nprintf("4th byte = 0x%02X\\n", (u.bytes)[3]);\n
Run Code Online (Sandbox Code Playgroud)\n示例输出:
\n\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0x00\n2nd byte = 0x00\n3rd byte = 0x04\n4th byte = 0xD2\n
如果您也想使用原始指针,则可以在没有联合的情况下完成此操作,如下所示:
\n技术 2:读取原始指针(这不是“类型双关语”):
\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
Run Code Online (Sandbox Code Playgroud)\n示例输出:
\n\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0x00\n2nd byte = 0x00\n3rd byte = 0x04\n4th byte = 0xD2\n
为了避免上述联合类型双关和原始指针方法都存在的字节顺序问题,您可以使用类似以下内容的方法。这避免了硬件架构之间的字节顺序差异:
\n技术 3.1:使用位掩码和位移位(这不是“类型双关”):
\n1st byte = 0x00\n2nd byte = 0x00\n3rd byte = 0x04\n4th byte = 0xD2\n
Run Code Online (Sandbox Code Playgroud)\n示例输出(上述技术与字节序无关!):
\n\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
或者:
\n技术 3.2:使用便捷宏进行位掩码和位移位:
\nuint32_t value = 1234;\nuint8_t *bytes = (uint8_t *)&value;\n\n// read individual bytes from uint32_t value\nprintf("1st byte = 0x%02X\\n", bytes[0]);\nprintf("2nd byte = 0x%02X\\n", bytes[1]);\nprintf("3rd byte = 0x%02X\\n", bytes[2]);\nprintf("4th byte = 0x%02X\\n", bytes[3]);\n
Run Code Online (Sandbox Code Playgroud)\n示例输出(上述技术与字节序无关!):
\n\n\nRun Code Online (Sandbox Code Playgroud)\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n---------------\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
否则,如果体系结构是Little-endian ,(my_pixel.RGBA)[0]
则 、 或(u.bytes)[0]
可能等于byte0
(如我上面所定义) ,或者如果体系结构是Big-endian ,则可能等于。byte3
请参阅下面的字节顺序图: https: //en.wikipedia.org/wiki/Endianness。请注意,在 big-endian 中,任何给定变量的最高有效字节首先存储在内存中(意味着:在较低地址中),但在 Little-endian 中,首先存储的是最低有效字节(在较低地址中)。地址)在内存中。另请记住,字节顺序描述的是字节顺序,而不是位顺序(字节内的位顺序与字节顺序无关),并且每个字节是 2 个十六进制字符,或“半字节”,其中半字节是 4 位。
\n\n根据上面的维基百科文章,网络协议通常使用大端字节顺序,而大多数处理器(x86、大多数 ARM 等)通常是小端字节顺序(强调):
\n\n\n大尾数法是网络协议中的主导顺序,例如在互联网协议套件中,它被称为网络顺序,首先传输最高有效字节。相反,小尾数法是处理器架构(x86、大多数 ARM 实现、基本 RISC-V 实现)及其相关内存的主要顺序。
\n
根据维基百科的“类型双关”文章,向工会成员写入value
但从中读取RGBA[4]
是“未指定的行为”。然而,@Eric Postpischil 在这个答案下面的评论中指出,维基百科是错误的。该答案顶部的其他参考文献也与现在编写的维基百科答案不一致。
我现在理解并同意Eric Postpischil 的评论,该评论指出(强调):
\n\n\n引用的文本涉及与除最后存储的联合成员之外的联合成员相对应的字节,不适用于这种情况。例如,适用于写入2字节成员、读出
\nshort
4字节成员的情况。int
额外的两个字节未指定。这提供了一个 C 实现许可证,可以将存储实现为short
两字节存储(保留联合的剩余字节不变)或四字节存储(可能是因为它对处理器来说是高效的)。在本例中,我们有一个四字节uint32_t
成员和一个四字节uint8_t [4]
成员。
维基百科声称(截至 2021 年 4 月 22 日):
\n对于工会:
\n1st byte = 0xD2\n2nd byte = 0x04\n3rd byte = 0x00\n4th byte = 0x00\n
Run Code Online (Sandbox Code Playgroud)\n\n\n\n
my_union.ui
在初始化另一个成员 后进行访问my_union.d
,仍然是 C 中类型双关[4]的一种形式,结果是未指定的行为 [5](以及C++ 中的未定义行为[6])。
来自上面的参考文献[5]:“未指定的行为”包括:
\n\n\n与除最后存储到 (6.2.6.1) 的联合成员之外的联合成员相对应的字节值。
\n
这意味着,如果您将数据存储到 union 的一个成员中,但从另一个成员中读取数据(这正是您想要使用该 union 的目的) ,则根据 C 标准,这是“未指定的行为”。
我认为 gcc 允许类型双关(写入联合的一个成员,但从联合中的另一个成员读取,作为“翻译”的一种形式)作为“gcc 扩展”,但是 C 和 C++ 标准,如果在您的中-Wpedantic
使用建立标志,否则禁止它。
READ_BYTE()
宏添加到我的eRCaGuy_hello_world存储库中的utilities.h文件中。 归档时间: |
|
查看次数: |
1893 次 |
最近记录: |