我的问题类似于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的系统上是未定义的行为,并且没有两个补码且它不能表示数字-128,因此它不能代表256个不同的字符。在foo
中,即使char已签名也会有效,因为C11标准(草案N1570)的第7.21.8.1节说明fread
使用unsigned char
而非char
和{{1必须能够表示0到255范围内的所有值。
尝试读取数字unsigned char
时,bar
是否确实会导致未定义的行为?如果有,仍有使用0x80
的解决方法?
编辑:我所指的未定义行为是由std::istream
引入istream::read
而不是从缓冲区转换为unsigned char。例如,如果它是符号+幅度机器并且char被签名则则0x80为负0,但是负0和正0必须始终根据标准进行比较。如果是这种情况,那么只有255个不同的签名字符,你不能用char表示一个字节。强制转换是有效的,因为在将签名转换为无符号时,它总是将buffer
添加到负数(C ++ 11标准N3242草案的第4.7节)。
答案 0 :(得分:3)
我想我有答案:bar
不会导致未定义的行为。
在接受的question答案中,R ..说:
在非二进制补码系统上,signed char不适合访问对象的表示。这是因为有两个可能的带符号的char表示具有相同的值(+0和-0),或者一个表示没有值(陷阱表示)。在任何一种情况下,这都会阻止您对对象的表示执行最有意义的操作。例如,如果您有一个16位无符号整数0x80ff,则作为有符号字符的一个或另一个字节将陷阱或比较等于0。
请注意,在这样的实现(非二进制补码)上,需要将plain char定义为无符号类型,以便通过char访问对象的表示以使其正常工作。虽然没有明确的要求,但我认为这是从标准中的其他要求中得出的要求。
这似乎是这种情况,因为C ++ 11(草案N3242)第3.9节第2段说:
对于普通可复制类型T的任何对象(基类子对象除外),无论对象是否保持类型T的有效值,组成对象的基础字节(1.7)都可以复制到数组中char或unsigned char。如果将char或unsigned char数组的内容复制回对象,则该对象应随后保持其原始值。
如果char
已签名且对某些值有多个对象表示(例如符号+幅度为0),那么如果将对象复制到char数组然后返回到对象中,则它可能不具有相同的值值后缀因为char数组可以更改为不同的对象表示。这与上面的引用相矛盾,因此如果机器的char
具有相同值表示的多个对象表示,则signed char
必须是无符号的(例如,在符号+值机器上,0x80和0x00都表示0)。这意味着bar
是定义的行为,因为它是未定义行为的唯一情况将要求char
被签名并且具有奇怪的表示,不符合标准的上述引用。