Is there an orthodox way to avoid compiler warning C4309 - "truncation of constant value" with binary file output?
Asked Answered
K

2

16

My program does the common task of writing binary data to a file, conforming to a certain non-text file format. Since the data I'm writing is not already in existing chunks but instead is put together byte by byte at runtime, I use std::ostream::put() instead of write(). I assume this is normal procedure.

The program works just fine. It uses both std::stringstream::put() and std::ofstream::put() with two-digit hex integers as the arguments. But I get compiler warning C4309: "truncation of constant value" (in VC++ 2010) whenever the argument to put() is greater than 0x7f. Obviously the compiler is expecting a signed char, and the constant is out of range. But I don't think any truncation is actually happening; the byte gets written just like it's supposed to.

Compiler warnings make me think I'm not doing things in the normal, accepted way. The situation I described has to be a common one. Is there are common way to avoid such a compiler warning? Or is this an example of a pointless compiler warning that should just be ignored?

I thought of two inelegant ways to avoid it. I could use syntax like mystream.put( char(0xa4) ) on every call. Or instead of using std::stringstream I could use std::basic_stringstream< unsigned char >, but I don't think that trick would work with std::ofstream, which is not a templated type. I feel like there should be a better solution here, especially since ofstream is meant for writing binary files.

Your thoughts?

--EDIT--

Ah, I was mistaken about std::ofstream not being a templated type. It is actually std::basic_ofstream<char>, but I tried that method that and realized it won't work anyway for lack of defined methods and polymorphic incompatibility with std::ostream.

Here's a code sample:

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
Kerenkeresan answered 18/3, 2013 at 0:2 Comment(1)
Just so we're all clear, can you add a concrete code example to your question?Sharpie
K
18

I found solution that I'm happy with. It's more elegant than explicitly casting every constant to unsigned char. This is what I had:

ss.put( 0xa4 ); // C4309

I thought that the "truncation" was happening in implicitly casting unsigned char to char, but Cong Xu pointed out that integer constants are assumed to be signed, and any one greater than 0x7f gets promoted from char to int. Then it has to actually be truncated (cut down to one byte) if passed to put(). By using the suffix "u", I can specify an unsigned integer constant, and if it's no greater than 0xff, it will be an unsigned char. This is what I have now, without compiler warnings:

ss.put( 0xa4u );
Kerenkeresan answered 18/3, 2013 at 20:49 Comment(0)
T
7
std::stringstream ss;
ss.put(0x7f);
ss.put(0x80); //C4309

As you've guessed, the problem is that ostream.put() expects a char, but 0x7F is the maximum value for char, and anything greater gets promoted to int. You should cast to unsigned char, which is as wide as char so it'll store anything char does and safely, but also make truncation warnings legitimate:

ss.put(static_cast<unsigned char>(0x80)); // OK
ss.put(static_cast<unsigned char>(0xFFFF)); //C4309
Tolliver answered 18/3, 2013 at 0:30 Comment(4)
Why do you use ss.put(static_cast<unsigned char>(0x80)); instead of ss.put(unsigned char(0x80)) or ss.put((unsigned char) 0x80)? Is a static cast preferred for some reason?Kerenkeresan
@SamKauffman, it's just the modern C++ way of doing casts. It's wordier but less ambiguous and safer.Disarticulate
@CongXu, thanks! What you wrote about promotion helped me understand what truncation is actually going on.Kerenkeresan
@SamKauffman ss.put(unsigned char(0xFFFF)); <--- no warning. That's why C++ casts are preferred over C casts.Tolliver

© 2022 - 2024 — McMap. All rights reserved.