使用reinterpret_cast的签名别名

时间:2019-05-24 15:09:39

标签: c++ language-lawyer memcpy signed reinterpret-cast

采用以下代码

#include <iostream>

void func() {
    int i = 2147483640;
    while (i < i + 1)
    {
        std::cerr << i << '\n';
        ++i;
    }

    return;
}

int main() {
    func(); 
}

该代码显然是错误的,因为while循环仅在有符号的int i溢出时才终止,这就是UB,因此编译器可以例如将其优化为无限循环(Clang在{{ 1}}),或做其他时髦的事情。现在我的问题是:从我对C ++标准的阅读中,等同于直至可能别名的类型(即指针-O3int*可以别名)。为了进行一些时髦的签名“包装”,以下内容是否具有未定义的行为?

unsigned*

我已经在#include <iostream> static int safe_inc(int a) { ++reinterpret_cast<unsigned&>(a); return a; } void func() { int i = 2147483640; while (i < safe_inc(i)) { std::cerr << i << '\n'; ++i; } return; } int main() { func(); } 上使用-O3参数在Clang 8和GCC 9上尝试了上面的代码,没有错误或警告,并且循环在包装到-Wall -Wextra -Wpedantic -O3 -fsanitize=address,undefined之后终止。

cppreference.com告诉我

  

类型别名

     

每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型的对象的存储值时,除非满足以下条件之一,否则行为是不确定的:

     
      
  • AliasedType是DynamicType的(可能是经过简历验证的)带符号或无符号的变体。
  •   

根据我的阅读,这意味着出于类型别名的目的,不考虑符号性,并且使用INT_MIN的代码具有定义明确的语义(尽管无论如何还是有些俗气)。

2 个答案:

答案 0 :(得分:6)

在此别名是完全合法的。参见http://eel.is/c++draft/expr.prop#basic.lval-11.2

  

如果程序尝试通过以下方式访问对象的存储值   一种glvalue,其类型与以下一种不相似([conv.qual])   以下类型的行为未定义:53

     

(11.1)对象的动态类型,

     

(11.2)一个类型,它是有符号或无符号类型   对应于对象的动态类型

我认为,也有必要讨论实际的溢出问题,它不一定需要reinterpret_cast。隐式积分转换可以达到相同的效果

 unsigned x = i;
 ++x;
 i = x; // this would serve you just fine.

此代码将是C ++ 20之前的实现定义,因为您将从无法由目标类型表示的值进行转换。

自C ++ 20起,此代码将格式正确。

请参见https://en.cppreference.com/w/cpp/language/implicit_conversion

另一方面,如果需要整数溢出语义,则最好从无符号类型开始。

答案 1 :(得分:5)

您的代码完全合法,cpp参考是一个很好的来源。您可以在标准[basic.lval]/11

中找到相同的信息
  

如果程序试图通过glvalue访问对象的存储值,该glvalue的类型与以下一种类型不相似([conv.qual]),则行为未定义:

     
      
  • 对象的动态类型

  •   
  • 一种类型,它是与对象的动态类型相对应的有符号或无符号类型,[...]

  •