【发布时间】:2015-10-04 10:09:59
【问题描述】:
在阅读有关别名的另一个问题 (What is the strict aliasing rule?) 及其最佳答案时,我意识到我仍然不完全满意,即使我认为我在那里理解了所有内容。
(这个问题现在被标记为 C 和 C++。如果您的答案只涉及其中之一,请说明是哪一个。)
所以我想了解如何在这方面进行一些开发,以激进的方式投射指针,但有一个简单的保守规则,以确保我不会引入 UB。我有一个关于这样一条规则的建议。
(更新:当然,我们可以避免所有类型的双关语。但这不是很有教育意义。当然,除了@987654323 之外,确实存在零个明确定义的例外@异常。)
更新 2:我现在明白为什么这个问题中提出的方法不正确。但是,了解是否存在简单、安全的替代方案仍然很有趣。截至目前,至少有一个答案提出了这样的解决方案。
这是原始示例:
int main()
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Alias that buffer through message
Msg* msg = (Msg*)(buff);
// Send a bunch of messages
for (int i =0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0] );
SendWord(buff[1] );
}
}
重要的一行是:
Msg* msg = (Msg*)(buff);
这意味着现在有两个(不同类型的)指针指向相同的数据。我的理解是,任何通过其中一个进行写入的尝试都会使另一个指针基本上无效。 (“无效”是指我们可以安全地忽略它,但通过无效指针读取/写入是 UB。)
Msg* msg = (Msg*)(buff);
msg->a = 5; // writing to one of the two pointers
SendWord(buff[0] ); // renders the other, buffer, invalid
因此,我建议的规则是,一旦您创建了第二个指针(即创建 msg),您应该立即并永久地“淘汰”另一个指针。
有什么比将指针设置为 NULL 更好的方法来引退指针:
Msg* msg = (Msg*)(buff);
buff = NULL; // 'retire' buff. now just one pointer
msg->a = 5;
现在,分配给msg->a 的最后一行不能使任何其他指针无效,因为当然没有。
接下来,当然,我们得想办法打电话给SendWord(buff[1] );。这不能立即完成,因为buff 已停用并且为 NULL。我现在的建议是再次投退。
Msg* msg = (Msg*)(buff);
buff = NULL; // 'retire' buff. now just one pointer
msg->a = 5;
buff = (uint32_t*)(msg); // cast back again
msg = NULL; // ... and now retire msg
SendWord(buff[1] );
总而言之,每次你在两个“不兼容”类型之间转换一个指针(我不确定如何定义“不兼容”?),那么你应该立即“淘汰”旧指针。如果这有助于您执行规则,请将其显式设置为 NULL。
这样够保守吗?
也许这太保守了,还有其他问题,但我首先想知道这是否足够保守,以避免通过违反严格的别名来引入 UB。
最后,回顾一下原始代码,修改为使用这条规则:
int main()
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Send a bunch of messages
for (int i =0; i < 10; ++i)
{ // here, buff is 'valid'
Msg* msg = (Msg*)(buff);
buff = NULL;
// here, only msg is 'valid', as buff has been retired
msg->a = i;
msg->b = i+1;
buff = (uint32_t*) msg; // switch back to buff being 'valid'
msg = NULL; // ... by retiring msg
SendWord(buff[0] );
SendWord(buff[1] );
// now, buff is valid again and we can loop around again
}
}
【问题讨论】:
-
经验法则:不要使用双关语。它的一些实例是明确定义的,但大多数不是,除非有一个很好的理由,否则您通常可以编写一个更漂亮的解决方案,而不涉及类型双关。
-
@FUZxxl,在某种意义上我同意。我从来没有这样做过,我现在也不需要这样做。但我很好奇。在我生命中的某个时候,我可能别无选择,只能稍微突破界限。如果每个人都说“不要这样做”,或者“它总是 UB”,那么我别无选择,只能编写代码并向我的老板说“好吧,它在我的测试中有效,所以我要必须使用它,因为我无法获得任何其他有用的建议” :-) .
-
@AaronMcDaid 好吧,你问的是“最简单的经验法则”,我给你的是最简单的经验法则吗?
-
此外,对这个问题进行双重标记会使问题加倍复杂,因为 C 中的严格别名规则与 C++ 中的不同。事实上,在 C++ 中,对于在 malloc 空间中进行别名处理时会发生什么,并没有明确说明。
标签: c++ c strict-aliasing