使用reinterpret_cast的签名别名

Jon*_*ler 12 c++ signed memcpy language-lawyer reinterpret-cast

采取以下代码

#include <iostream>

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

    return;
}

int main() {
    func(); 
}
Run Code Online (Sandbox Code Playgroud)

这段代码显然是错误的,因为while循环仅在有符号的int i溢出(即UB)时才能终止,因此编译器可以例如将其优化为无限循环(Clang在上进行-O3)或执行其他类型的时髦操作。现在我的问题是:从我对C ++标准的阅读中,等同于签名的类型可能会别名(即指针int*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(); 
}
Run Code Online (Sandbox Code Playgroud)

我已经在Clang 8和GCC 9上-O3使用-Wall -Wextra -Wpedantic -O3 -fsanitize=address,undefined参数尝试了上面的代码,没有错误或警告,并且循环到包装后终止INT_MIN

cppreference.com告诉我

类型别名

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

  • AliasedType是DynamicType的(可能是cv限定的)带符号或无符号的变体。

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

Ser*_*eyA 6

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

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

(11.1)对象的动态类型,

(11.2)是与对象的动态类型相对应的有符号或无符号类型的类型

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

 unsigned x = i;
 ++x;
 i = x; // this would serve you just fine.
Run Code Online (Sandbox Code Playgroud)

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

从C ++ 20开始,此代码将格式正确。

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

附带说明一下,如果您希望整数溢出语义,则最好从无符号类型开始。

  • 尽管它不能回答有关别名的问题 (3认同)

Oli*_*liv 5

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

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

  • 对象的动态类型,

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

  • @LightnessRacesinOrbit http://eel.is/c++draft/basic.fundamental#2。Cpp参考更喜欢普通英语,其目的当然是要比标准更易于阅读。 (2认同)