函数返回值上的位运算符晋升

3
使用代码:
#include <cstdint>

uint8_t a() { return 5; }

auto b() {
    uint8_t c = 6;
    c |= a();  // Warning here
    return c;
}

auto d() {
    uint8_t c = 6;
    uint8_t d = a();
    c |= d;
    return c;
}
警告(使用-Wconversion):
<source>:7:12: warning: conversion from 'int' to 'uint8_t' {aka 'unsigned char'} may change value [-Wconversion]

我认为问题与位运算的整数提升有关,但在第二个函数d()中,我首先将其赋值给一个变量,然后就没有警告了。(clang不会警告,只有g++会)
这可以通过强制转换而不是变量赋值来解决吗?
为什么在使用函数时行为会有所不同?
带上以上代码的编译器探索链接: https://godbolt.org/z/q9eMVT

1
这看起来像是gcc的一个bug - 警告是虚假的。当该行被重写为语义等效的c = c | a;时,同样的gcc不会发出警告。 - SergeyA
在使用gcc 8.3时,这里竟然没有任何警告hmm - 463035818_is_not_a_number
啊,没有警告,因为我忘记了加上“-Wconversion”。 - 463035818_is_not_a_number
1
一般来说,在位运算期间要注意整数提升规则。 - Cory Kramer
1个回答

2
我认为这是GCC的一个错误。根据 [expr.ass]/7,表达式
x |= y

等同于

x = x | y

除了 a 只被评估一次之外,在按位包含 OR 中,像其他位运算一样,正如 CoryKramer 在上面的评论中所指出的那样,通常算术转换会首先在两个操作数上执行 [expr.or]/1。由于我们的两个操作数都是 std::uint8_t 类型,因此通常的算术转换只是整数提升 [expr.arith.conv]/1.5。在 std::uint8_t 上进行整数提升应该意味着将两个操作数都转换为 int [conv.prom]/1。由于两个操作数的转换类型相同,不需要对操作数进行进一步的转换。最后,| 表达式生成的 int 被转换回 std::uint8_t 并存储在 x 所引用的对象中 [expr.ass]/3。我认为,这最后一步是在某些情况下触发警告的原因。然而,无论我们是否沿途转换为 int保证更大),两个 std::uint8_t 之间的按位逻辑 OR 的结果都不可能无法表示为 std::uint8_t。因此,这里的警告是不必要的,这可能是为什么通常不会产生警告的原因。
我看到你第一版和第二版之间唯一的区别是a()是一个右值,而d是一个左值。然而,值类别对通常算术转换的行为应该没有影响。因此,警告-不必要或不-至少应该始终生成。正如您自己所指出的,其他编译器(如clang)在这里不会产生警告。此外,该问题似乎与复合赋值中涉及函数调用有关。如上面SergeyA在另一条评论中所指出的那样,GCC不会在等效形式c = c | a()中产生警告。可以使用其他类型的右值来代替函数调用,例如字面量的强制转换结果。
c |= static_cast<std::uint8_t>(42);

GCC也不会产生警告。但是,一旦表达式的右侧有函数调用,即使函数调用的结果根本没有被使用,例如在…”
c |= (a(), static_cast<std::uint8_t>(5));

the warning appears”,因此我认为这是GCC的一个错误,如果您能够撰写一个错误报告,那将非常好。…”

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