有没有一种正统的方法来避免编译器警告C4309 - “截断常量值”与二进制文件输出?

16
我的程序执行了一个常见的任务,即将二进制数据写入文件,遵循特定的非文本文件格式。由于我要写入的数据不是已经存在的块,而是在运行时一个字节一个字节地组合起来,所以我使用std::ostream ::put()而不是write()。我认为这是正常的过程。
该程序工作得很好。它使用std::stringstream :: put()std::ofstream :: put(),并以两位十六进制整数作为参数。但是每当put()的参数大于0x7f时,我会收到编译器警告C4309:“截断常量值”(在VC ++ 2010中)。显然,编译器期望是signed char,而该常量超出范围。但我认为实际上没有发生任何截断。字节按预期被写入。
编译器警告让我觉得我没有按照正常、接受的方式做事。我描述的情况必须是普遍存在的。有没有一种常见的方法可以避免这样的编译器警告?还是这只是一个无意义的编译器警告,应该被忽略?
我想到了两种不优雅的方法来避免它。我可以在每次调用时使用类似mystream.put(char(0xa4))的语法。或者,我可以使用std::basic_stringstream< unsigned char >而不是std::stringstream,但我认为这个技巧在std::ofstream中不起作用,因为它不是模板化类型。我感觉应该有一个更好的解决方案,特别是因为ofstream用于写入二进制文件。
你的想法?
-编辑-
啊,关于std :: ofstream不是模板化类型,我弄错了。实际上,它是std :: basic_ofstream< char>,但我尝试了那种方法,并意识到由于缺乏定义的方法和与std :: ostream的多态性不兼容,它也无法工作。
这是一个代码示例:
stringstream ss;
int a, b;
/* Do stuff */
ss.put( 0 );
ss.put( 0x90 | a ); // oddly, no warning here...
ss.put( b );        // ...or here
ss.put( 0xa4 );     // C4309

4
为了明确问题,您能否在问题中添加一个具体的代码示例? - Oliver Charlesworth
2个回答

18

我找到了一个让我满意的解决方案。它比将每个常量显式转换为unsigned char更加优雅。这就是我的代码:

ss.put( 0xa4 ); // C4309
我以为“截断”是由于将unsigned char隐式转换为char时发生的,但徐聪指出整数常量被认为是有符号的,大于0x7f的任何一个常量都会从char提升到int。如果传递给put(),它必须被截断(缩减为一个字节)。通过使用后缀“u”,我可以指定无符号整数常量,如果它不超过0xff,则将成为unsigned char。这是我目前的代码,没有编译器警告:
ss.put( 0xa4u );

7
std::stringstream ss;
ss.put(0x7f);
ss.put(0x80); //C4309

正如你所猜测的那样,问题在于 ostream.put() 期望一个 char 类型的值,但是 0x7Fchar 类型的最大值,任何比它更大的值都会被提升为 int 类型。你应该将其转换为 unsigned char 类型,它与 char 类型一样宽,因此可以安全地存储 char 类型的任何值,并且还可以使截断警告合法化:
ss.put(static_cast<unsigned char>(0x80)); // OK
ss.put(static_cast<unsigned char>(0xFFFF)); //C4309

1
为什么你使用 ss.put(static_cast<unsigned char>(0x80)); 而不是 ss.put(unsigned char(0x80)) 或者 ss.put((unsigned char) 0x80)?是否有某些原因偏好于静态转换? - Sam Kauffman
1
@SamKauffman,这只是现代C++中cast的一种方式。它更啰嗦但更不含糊和更安全。 - Mark Ransom
@CongXu,谢谢!你关于推广所写的内容帮助我理解了实际上正在发生的截断。 - Sam Kauffman
2
@SamKauffman ss.put(unsigned char(0xFFFF)); <--- 没有警告。这就是为什么C++强制类型转换比C强制类型转换更受欢迎的原因。 - congusbongus

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