将int32类型转换为uint32类型是否无操作?

4

我想将一个int32_t的位粘贴到uint32_t类型中,不进行任何转换,只是重新解释。以下代码恰好实现了我的要求:

int32_t  iA = -1;
uint32_t uA = *(uint32_t*)&iA;

但我在想,以下更容易编写的转换是否可靠,它会生成相同或更少的汇编代码,理想情况下只使用 mov 指令? (即,它永远不会对其执行 "数学" 运算,保持底层位不变.)

int32_t  iB = -1;
uint32_t uB = (uint32_t)iB;

assert(uA == uB); // ?

1
@AnneQuinn 标准根本没有提到汇编,它只描述了程序应该如何运行。我在GCC上得到了相同的结果(少了一条指令),而Clang为两者生成了相同的汇编代码。 - HolyBlackCat
5
我会使用memcpy,它保证不改变任何位。 - alain
1
uint32 uA = *(uint32*)&iA; 像这样的类型转换在C++中几乎总是未定义的行为 - 虽然我对这种情况并不完全确定。无论如何,除非您确信并且已经仔细检查了语言或编译器明确允许这样做,否则根本不要考虑这样做。另一方面,将基本类型强制转换始终是安全的,尽管我建议使用 static_cast。毕竟这不是C。 - besc
3
@besc 这是可以将有符号/无符号类型进行别名处理的。 - rustyx
2
@AnneQuinn 在二进制补码结构(即所有现代结构)上,它总是不执行任何操作。此外,C++20将要求使用二进制补码。 - HolyBlackCat
显示剩余6条评论
2个回答

7

在C++20之前,有符号整数的表示是实现定义的。但是,std::intX_t在C++20之前也被保证具有2s'补码表示:

int8_tint16_tint32_tint64_t - 分别为宽度恰好为8、16、32和64位的有符号整数类型,不含填充位,并使用2's补码表示负值(仅在实现直接支持该类型时提供)

当您编写以下代码时:

std::int32_t  iA = -1;
std::uint32_t uA = *(std::uint32_t*)&iA;

您可以获得所有位设置的值。 标准指出,如果“类型类似于...与对象的动态类型相对应的有符号或无符号类型”,则可以通过std::uint32_t*类型的指针访问std::int32_t。 因此,严格来说,在取消引用指针之前,我们必须确保std::uint32_t确实是对应于std::int32_t的无符号类型:

static_assert(std::is_same_v<std::make_unsigned_t<std::int32_t>, std::uint32_t>);

当你编写代码时
std::int32_t  iB = -1;
std::uint32_t uB = (std::uint32_t)iB;

你依赖于将值转换为无符号类型,这是明确定义的并保证产生相同的值
至于汇编语言,两个强制转换都不会产生任何操作:
std::uint32_t foo() {
    std::int32_t  iA = -1;
    static_assert(std::is_same_v<std::make_unsigned_t<std::int32_t>, std::uint32_t>);
    return *(std::uint32_t*)&iA;
}

std::uint32_t bar() {
    std::int32_t  iB = -1;
    return (std::uint32_t)iB;
}

result in:

foo():
        mov     eax, -1
        ret
bar():
        mov     eax, -1
        ret

3
使用memcpy是避免类型别名时产生未定义行为的常见解决方案。 评论中指出,仅在有符号性质不同的类型别名是可以的,但对于例如floatint则不是这种情况。 只要对象表示对于该类型是有效的,memcpy的工作就可以了。 编译器在优化memcpy调用方面非常擅长,在这种情况下,调用被完全优化掉了

如果我能够接受多个答案…… 我最终使用了 memcpy,静态类型转换用于类型玩弄是一个坏主意,但我对问题的措辞过于狭隘。 - Anne Quinn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接