c++ - 使用 iostream read 和 signed char 时的未定义行为

标签 c++

我的问题类似于this但更具体一点。我正在编写一个函数来从使用小端表示的 istream 中读取一个 32 位无符号整数。在 C 中,这样的事情会起作用:

#include <stdio.h>
#include <inttypes.h>

uint_least32_t foo(FILE* file)
{
    unsigned char buffer[4];
    fread(buffer, sizeof(buffer), 1, file);

    uint_least32_t ret = buffer[0];
    ret |= (uint_least32_t) buffer[1] << 8;
    ret |= (uint_least32_t) buffer[2] << 16;
    ret |= (uint_least32_t) buffer[3] << 24;
    return ret;
}

但是如果我尝试使用 istream 做类似的事情,我会遇到我认为未定义的行为

uint_least32_t bar(istream& file)
{
    char buffer[4];
    file.read(buffer, sizeof(buffer));

    // The casts to unsigned char are to prevent sign extension on systems where
    // char is signed.
    uint_least32_t ret = (unsigned char) buffer[0];
    ret |= (uint_least32_t) (unsigned char) buffer[1] << 8;
    ret |= (uint_least32_t) (unsigned char) buffer[2] << 16;
    ret |= (uint_least32_t) (unsigned char) buffer[3] << 24;
    return ret;
}

在 char 有符号且没有二进制补码的系统上,这是未定义的行为,它不能表示数字 -128,因此它不能表示 256 个不同的字符。在 foo 中,即使 char 已签名,它也能正常工作,因为 C11 标准(草案 N1570)的第 7.21.8.1 节说 fread 使用 unsigned char not charunsigned char 必须能够表示 0 到 255(含)范围内的所有值。

当尝试读取数字 0x80 时,bar 是否真的会导致未定义的行为?如果是,是否还有仍然使用 std::istream 的解决方法>?

编辑: 我指的未定义行为是由 istream::readbuffer 而不是从 buffer 到 unsigned 的转换引起的字符。例如,如果它是一个 sign+magnitude 机器并且 char 是有符号的,那么 0x80 是负 0,但是根据标准负 0 和正 0 必须始终比较相等。如果是这种情况,那么只有 255 个不同的带符号字符,您不能用字符表示一个字节。转换会起作用,因为它总是将 UCHAR_MAX + 1 添加到负数(草案 C++11 标准 N3242 的第 4.7 节)时将有符号转换为无符号。

最佳答案

我想我有答案了:bar 不会导致未定义的行为。

在这个 question 的接受答案中, R.. 说:

On a non-twos-complement system, signed char will not be suitable for accessing the representation of an object. This is because either there are two possible signed char representations which have the same value (+0 and -0), or one representation that has no value (a trap representation). In either case, this prevents you from doing most meaningful things you might do with the representation of an object. For example, if you have a 16-bit unsigned integer 0x80ff, one or the other byte, as a signed char, is going to either trap or compare equal to 0.

Note that on such an implementation (non-twos-complement), plain char needs to be defined as an unsigned type for accessing the representations of objects via char to work correctly. While there's no explicit requirement, I see this as a requirement derived from other requirements in the standard.

这似乎是因为 C++11(草案 N3242)第 3.9 节第 2 段说:

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

如果 char 被签名并且有多个对象表示某个值(例如 0 in sign+magnitude)然后如果对象被复制到一个 char 数组然后返回到对象中,它可能不会后记具有相同的值,因为 char 数组可以更改为不同的对象表示形式。这与上面的引用相矛盾,所以如果机器的 signed char 有多个对象表示相同的值表示,那么 char 必须是无符号的(例如,在 sign+value 机器上都是 0x80而 0x00 代表 0)。这意味着 bar 是已定义的行为,因为它是未定义行为的唯一情况是要求 char 已签名并且具有奇怪的表示形式,无法满足上述引用标准。

关于c++ - 使用 iostream read 和 signed char 时的未定义行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26967511/

相关文章:

c++ - QPdfWriter 和页面大小

c++ - 如何使用 WriteConsoleInput/WriteConsole 将 Return/Enter 字符发送到控制台

c++ - 在外部函数模板中调用类模板的函数模板

c++ - 关于如何使用 Qt 模型 View 类的建议

c++ - 断然阻止或阻止带有特定参数的对象实例化

c++ - C++ 复制构造函数有什么大惊小怪的?

c++ - 通过 CreateProcessW 使用 "mkdir"创建的目录名称中的垃圾?

c++ - OpenGL 缺少 Glut32

c++ - 我什么时候应该在 Qt 上使用 qApp->setProperty

c++ - 具有任意数量基本类型属性的对象的 std::hash 变体