我在遗留代码中遇到一种情况,其中一个结构的大字段被分成两个子字段。例如,uint32
被分为两个uint16
:
typedef struct
{
uint16 myVar_H;
uint16 myVar_L;
} MyStruct;
到目前为止,这些字段是通过转移到每个uint32
部分来从uint16
变量中读取的。现在我们要对此进行优化,以便通过将第一个字段的地址转换为uint32
来读取整个uint32
:
MyStruct s;
*((uint32*)(&s.myVar_H)) = myValue;
这适用于我的编译器。问题是这是否合法并在C89(或其他C标准)中定义?
注意:更改结构是另一种选择,但由于这是一个基于专有协议的古老遗留代码,我宁愿不触及结构定义。
编辑:我使用的是大端MCU(ColdFire),所以这在字节方面可以正常使用。
答案 0 :(得分:4)
您的"优化"存在一些问题。溶液:
myVar_H
和myVar_L
在内存中是连续的:允许编译器在两个字段之间添加填充。实现目标的法律和平台独立方式是保持以前的解决方案不变。它不应该带来任何性能问题。
MyStruct s;
// Reading
uint32 u = (uint32)s.myVar_H << 16 | s.myVar_L; // The cast is important
// Writing
s.myVar_H = myValue >> 16;
s.myVar_L = myValue % 0xFFFF;
编辑以下评论:您说您在大端计算机上工作,因此内存顺序不是问题。在这种情况下,最合法的&#34;您可能要做的事情是使您的结构union
具有匿名结构(不是实际C89
,它是gcc
扩展名。这样您就不会破坏严格的别名,新定义也不会破坏任何现有代码。
typedef union // Your type is now a union
{
struct
{
uint16 myVar_H;
uint16 myVar_L;
}; // Anonymous struct, legal C11 but gcc extension in C89
uint32 myVar;
} MyStruct;
s.myVar = myValue; // Safe
答案 1 :(得分:-2)
关于您的具体问题:
问题是这是否合法并在C89中定义?
答案是 否 :它违反了严格的别名规则。见What is the strict aliasing rule?。编译器可以随意做任何事情,包括完全忽略语句:
*((uint32*)(&s.myVar_H)) = myValue;
更安全的方法是使用memcpy
;您的代码段变为:
MyStruct s;
memcpy(&s.myVar_H, &myValue, sizeof myValue);
当然,在此特定情况下,&s.myVar_H
与&s
相同。
这假定myValue
必须具有正确的尺寸和格式(例如,它不能是char
或float
。事实并非如此,如果你有C99的复合文字,你可以写:
memcpy(&s.myVar_H, (uint32[]){myValue}, sizeof(uint32));
这很难看,但你可以在宏中隐藏它的丑陋:
#define LEGACY_CPY32(dst, src) memcpy(&(dst), (uint32[]){src}, sizeof(uint32))
但是这些建议会避免 严格别名问题。 Litte-endian问题和结构填充问题仍然存在,如果您切换机器和/或编译器,甚至只是更改编译器开关,您的代码可能会中断。
更新#1 :当然没有人会像这样写新代码。但我假设OP的目标是使用 最小 更改次数来更新正常运行的代码,而不是完全重写。< / p>
另见:
更新#2 :也许我可以提出一个更有说服力的案例?对于任何构造,人们都可以争论:
对于第三个标准,优雅,我的建议失去了手,与例如工会双关语。
对于第二个标准,效率,Embedded in Academia, "Type Punning, Strict Aliasing, and Optimization"(2013年6月)中的John Regehr对类似问题进行了陈述:
GCC 4.8.1和Clang~3.3(两者都适用于x86-64 Linux)的编译器无法为
c2
生成良好的代码[工会类型惩罚功能的一个例子] ...... [和蹩脚的目标代码
而在他的示例c5
中使用memcpy
:
两个编译器都非常了解
memcpy
,我们得到了所需的[最佳]目标代码。在我看来,
c5
是最容易理解这一小部分功能的代码,因为它不会造成混乱的转移,而且它完全显然没有可能由于工会的混乱规则和严格的别名。几年前,当我发现编译器可以看到memcpy
并生成正确的代码时,它成为了我最喜欢的打字习惯。
最后,让我们看看第一个标准,合法性。 Regehr声明关于工会双关语(强调补充):
不幸的是,这个代码[
c2
,使用union punning]也是 undefined 的现代C / C ++方言(或者只是未指定,我不是完全肯定)。
我不愿意与Regehr意见不一致,但Stack Overflow的共识似乎是因为C99之后工会投注已经有效;见:
但是在C89中它是不是定义的行为,OP特别询问。
最后注意OP请求:
由于这是一个基于专有协议的古老遗留代码,我宁愿不触及结构定义
memcpy
解决方案使头文件不受影响,如果您与一群敏感的程序员合作,这是有利的。您可以通过将宏LEGACY_CPY32
重新定义为之前的“不正确”但工作功能轻松退出所有更改:
#define LEGACY_CPY32(dst, src) (*((uint32*)(&(dst))) = (src))