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

qbt*_*937 7 c++

我的问题与相似,但有点具体.我正在编写一个函数来读取使用little endian表示的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;
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试使用一个类似的东西,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;
}
Run Code Online (Sandbox Code Playgroud)

它是在签名char的系统上的未定义行为,并且没有两个补码且它不能代表数字-128,因此它不能代表256个不同的字符.在foo当炭因(N1570草案)的C11标准第7.21.8.1签署,它仍然工作说,fread使用unsigned charcharunsigned char必须能够代表范围为0所有值设置为255的包容性.

是否bar尝试读取号码时确实会导致不确定的行为,0x80如果是的话是有依然采用了一种变通方法std::istream

编辑:未定义行为我指的是由导致istream::readbuffer不从缓冲区中投为unsigned char.例如,如果它是符号+幅度机器并且char被签名则则0x80为负0,但是负0和正0必须始终根据标准进行比较.如果是这种情况,那么只有255个不同的签名字符,你不能用char表示一个字节.演员阵容将起作用,因为在将UCHAR_MAX + 1签名转换为无符号时,它总是会添加负数(草案C++ 11标准N3242的第4.7节).

qbt*_*937 3

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

在这个问题的公认答案中,R.. 说:

在非补码系统上,signed char 不适合访问对象的表示。这是因为要么有两种可能具有相同值(+0 和 -0)的有符号字符表示,要么有一种没有值的表示(陷阱表示)。无论哪种情况,这都会阻止您对对象的表示进行最有意义的操作。例如,如果您有一个 16 位无符号整数 0x80ff,则一个或另一个字节(作为有符号字符)将捕获或比较等于 0。

请注意,在这样的实现(非二进制补码)中,需要将纯 char 定义为无符号类型,以便通过 char 访问对象的表示才能正常工作。虽然没有明确的要求,但我认为这是从标准中的其他要求派生的要求。

情况似乎如此,因为 C++11(草案 N3242)第 3.9 节第 2 段说:

对于普通可复制类型 T 的任何对象(基类子对象除外),无论该对象是否持有类型 T 的有效值,组成该对象的底层字节 (1.7) 都可以复制到 char 或无符号字符。如果将 char 或 unsigned char 数组的内容复制回对象中,则该对象随后应保留其原始值。

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