我的问题类似于this,但有点具体 . 我正在编写一个函数来读取使用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;
}
但是,如果我尝试使用 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的系统上的未定义行为,并且没有't two' s补码且它不能表示数字-128,因此它不能代表256个不同的字符 . 在 foo
中,即使char已签名也会起作用,因为C11标准(草案N1570)的第7.21.8.1节说 fread
使用 unsigned char
而不是 char
并且 unsigned char
必须能够表示0到255(包括0和255)范围内的所有值 .
当尝试读取数字 0x80
时, bar
确实会导致未定义的行为吗?如果是这样,是否仍有使用 std::istream
的变通方法?
Edit: 我所指的未定义行为是由 istream::read
引入 buffer
而不是从缓冲区转换为unsigned char . 例如,如果它是符号幅度的机器并且char被签名则则0x80为负0,但是负0和正0必须始终根据标准进行比较 . 如果是这种情况,那么只有255个不同的签名字符,你不能用char表示一个字节 . 强制转换是有效的,因为在转换为无符号时,它总是将 UCHAR_MAX + 1
添加到负数(草案C 11标准N3242的第4.7节) .
1 回答
我想我有答案:
bar
不会导致未定义的行为 .在question的接受答案中,R ..说:
这似乎是这种情况,因为C11(N3242草案)第3.9节第2段说:
如果
char
已签名并且具有某个值的多个对象表示(例如符号幅度为0),那么如果将对象复制到char数组然后返回到对象中,则可能后面的字段可能没有相同的值,因为char数组可能会更改到不同的对象表示 . 这与上面的引用相矛盾,因此如果机器的signed char
具有相同值表示的多个对象表示,则char
必须是无符号的(例如,在符号值机器上,0x80和0x00都表示0) . 这意味着bar
是已定义的行为,因为它是未定义行为的唯一情况将要求char
已签名且具有奇数表示,将不满足标准的上述引用 .