比较8位不同符号类型(int8_t, uint8_t):是结果确定性的

Comparing 8 bits types of different signedness (int8_t, uint8_t): is result deterministic?

本文关键字:uint8 结果 确定性 8位 符号 类型 比较 int8      更新时间:2023-10-16

我运行这个简单的程序:

#include <stdint.h>
#include <iostream>
int main() {
    uint8_t x = 100;
    int8_t y = -128;
    if (x < y) {
        std::cout << (int) x << " is less than " << (int) y << std::endl;
    } else {
        std::cout << (int) y << " is less than " << (int) x << std::endl;
    }
}

正确输出:

-128 is less than 100

一开始我很惊讶地发现没有生成签名警告。
然后我很惊讶没有错误的转换正在进行(-128 -> 255),因此没有得到错误的结果。
然后我读到这个:

1除bool、char16_t、char32_t或wchar_t以外的整型右值,如果整型能表示源类型的所有值,则可将其转换为整型右值,其整型转换等级(4.13)小于int;否则,源右值可以转换为unsigned int类型的右值。(§4.5)

链接到标准n2356
"可以转换"是什么意思?它是由编译器实现,如果这种转换将发生,因此,如果这个表达式将返回一个正确的值?

关键是编译器搜索一个公共类型来转换这两个操作数,但我没有发现标准中有任何义务要求尽其所能使这个公共类型能够表示这两个输入类型的所有可能值。

注意:我还标记了C,因为这种情况似乎也适用于它。
相关问题:有符号和无符号字符的比较。也这样。

是的,结果是确定的,而不是(编译器的)实现定义的。这里遵循c++ 11的动机(应该可以为其他版本做同样的事情),遵循此处的文档(此处建议链接)

有必要将以下所有内容结合起来:

5.9关系运算符
  1. […]

  2. 通常的算术转换在算术或枚举类型的操作数上执行。

要找到常用的算术转换,我们需要翻到第5章第9段的开头:

许多要求操作数为算术或枚举类型的二元操作符会导致转换并产生结果结果以类似的方式类型。目的是产生一个公共类型,这也是结果的类型。此模式称为常规算术转换,定义如下:

  • […(枚举和浮点类型)
  • 否则,积分提升(4.5)将同时在操作数[59]。那么以下规则将适用于提升的操作数:

    • 如果两个操作数的类型相同,则不需要进一步的转换。
    • […]

所以,积分提升,引用4.5:

非 bool、char16_t、char32_t或wchar_t的整型右值如果int可以表示所有,则可以将其转换为int类型的右值源类型的值;否则,源右值可以转换为unsigned类型的右值int。

:
我们有一个关系运算符,将使用通常的算术转换。这些要求应用积分提升。uint8_tint8_tint的积分提升是可能的,因此必须采用

因此,编译器将一个uint8_tint8_t的比较转换为两个int的比较。不存在不确定性行为

这里有一个类似的Q/a(关于short类型),它引导我走向正确的道路。

注意以下矛盾:关系操作符返回布尔值(5.9.1),但它们使用通常的算术转换,用于获得相同类型的两个操作数。但是,问题就在这里,通常的算术转换的定义说,公共类型也是结果的类型,这与关系操作符的情况不同!

对于C11不存在矛盾,关系操作符返回的结果确实是一个int。(感谢chux)