C++ 避免违反严格别名规则的最简单经验法则?
当我读到另一个关于别名()的问题和它的首要答案时,我意识到我仍然不完全满意,尽管我认为我已经完全理解了它C++ 避免违反严格别名规则的最简单经验法则?,c++,c,strict-aliasing,C++,C,Strict Aliasing,当我读到另一个关于别名()的问题和它的首要答案时,我意识到我仍然不完全满意,尽管我认为我已经完全理解了它 (这个问题现在被标记为C和C++)。如果你的答案只是其中之一,请澄清哪一个。 因此,我想了解如何在这方面进行一些开发,以积极的方式投射指针,但使用一个简单的保守规则,确保我不会引入UB。我这里有一个这样一个规则的建议 (更新:当然,我们可以避免所有类型的双关语。但这并不是很有教育意义。当然,除非在联合例外之外,存在严格定义的零例外。) 更新2:我现在明白了为什么这个问题中提出的方法不正确。然
(这个问题现在被标记为C和C++)。如果你的答案只是其中之一,请澄清哪一个。 因此,我想了解如何在这方面进行一些开发,以积极的方式投射指针,但使用一个简单的保守规则,确保我不会引入UB。我这里有一个这样一个规则的建议
(更新:当然,我们可以避免所有类型的双关语。但这并不是很有教育意义。当然,除非在联合
例外之外,存在严格定义的零例外。)
更新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
),就应该立即永久地“退出”另一个指针
有什么比将指针设置为NULL更好的方法使其失效:
Msg* msg = (Msg*)(buff);
buff = NULL; // 'retire' buff. now just one pointer
msg->a = 5;
现在,分配给msg->a
的最后一行不能使任何其他指针无效,因为,当然,没有指针
接下来,当然,我们必须找到一种方法来调用SendWord(buff[1])代码>。无法立即执行此操作,因为buff
已失效且为空。我现在的建议是重新考虑
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
}
}
intmain()
{
//从系统中获取32位缓冲区
uint32_t*buff=malloc(sizeof(Msg));
//发一串信息
对于(int i=0;i<10;++i)
{//这里,buff是“有效的”
味精*味精=(味精*)(浅黄色);
buff=NULL;
//在这里,只有消息是“有效的”,因为buff已经退役
msg->a=i;
msg->b=i+1;
buff=(uint32\u t*)msg;//切换回buff为“有效”
msg=NULL;//…通过使msg退役
发送字(buff[0]);
SendWord(buff[1]);
//现在,buff再次有效,我们可以再次循环
}
}
我的理解是,任何试图通过其中一个
将使另一个指针实质上无效
只要不访问类型为punned的指针,另一个“官方”指针就可以了。然而,如果你这样做,它将导致未定义的行为,这可能只是工作,做你所说的或其他事情,包括使其他指针无效。编译器可以随心所欲地处理UB
根据以下标准,使buff
成为指向Msg
的有效指针的唯一方法是memcpy
/memmove
:
memcpy( (void*)msg, (const void*) buff, sizeof (*msg));
此外,触发UB的不仅是写入,还包括读取或任何其他访问对象的方式:
如果程序试图通过访问对象的存储值
除以下类型之一之外的左值行为为
未定义的:
一些编译器还允许“挂起”该规则,如GCC、clang和ICC(可能也是MSVC),但这不能被视为可移植或标准行为。
深入分析了进一步的技术及其代码生成分析
你真的需要打破严格的别名规则吗?
大多数时候,不,你不需要。要克服这个问题,必须有完善的法律解决办法。
在上述情况下,只需在结构中存储一个普通指针
,并以确定的格式发送每个成员 C++回答:那不行。C++严格混叠规则明确地列举了可以用来访问对象的类型。如果您使用不同的类型,您将获得UB,即使您已经“退出”了所有不同类型的访问方法。根据C++14(n4140)3.10/10,允许的类型为:
如果程序试图通过glvalue访问对象的存储值,而不是
以下类型的行为未定义:
- 对象的动态类型
- 对象动态类型的cv限定版本
- 与对象的动态类型类似的类型(如4.4中所定义)
- 与对象的动态类型相对应的有符号或无符号类型
- 与动态类型的cv限定版本相对应的有符号或无符号类型
对于对象
- 在其元素或非静态元素中包含上述类型之一的聚合或联合类型
数据成员(递归地包括子集合的元素或非静态数据成员
或包含的联盟)
- 是对象动态类型的基类类型(可能是cv限定的)
字符
或无符号字符
类型
根据4.4,“类似类型”涉及修改多级指针的cv鉴定
所以,如果你
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
}
}
memcpy( (void*)msg, (const void*) buff, sizeof (*msg));
1: int *some_buff = malloc(sizeof(whatever));
2: memset(some_buff,0,sizeof(whatever));
3: while (some_buff[0] == 0)
4: {
5: whatever *manipulator = (whatever*)some_buff;
6: manipulate(manipulator);
7: }
int *some_buff = malloc(sizeof(whatever));
memset(some_buff,0,sizeof(whatever));
whatever *manipulator = (whatever*)some_buff;
manipulate(manipulator);
printf("%d\n",some_buff[0]);