__int64 to CString returns wrong values - C++ MFC
Asked Answered
C

3

7

I want to convert a __int64 variable into a CString. The code is exactly this

__int64 i64TotalGB;
CString totalSpace;
i64TotalGB = 150;
printf("disk space: %I64d GB\n", i64TotalGB);
totalSpace.Format(_T("%I64d", i64TotalGB));
printf("totalSpace contains: %s", totalSpace);

the first printf prints

"disk space: 150GB"

which it's correct, but the second printf prints randomly high numbers like

"totalSpace contains: 298070026817519929"

I also tried to use a INT64 variable instead of a __int64 variable, but the result is the same. What can be the cause of this?

Comeuppance answered 25/9, 2018 at 9:28 Comment(1)
General advice: Stop pretending, that you were targeting Win9x. You aren't. Use CStringW in place of CString, replace _T and similar macros with an L prefix, and live a happy life ever after. In other words: CStringW totalSpace; totalSpace.Format(L"%I64d", i64TotalGB); wprintf(L"totalSpace contains: %s", totalSpace.GetString());.Garin
C
10

Here:

totalSpace.Format(_T("%I64d", i64TotalGB));

you're passing i64TotalGB as an argument to the _T() macro instead of passing it as the second argument to Format().

Try this:

totalSpace.Format(_T("%I64d"), i64TotalGB);

Having said that, thanks to MS's mess (ha) around character encodings, using _T here is not the right thing, as CString is composed of a TCHAR and not _TCHAR. So taking that into account, might as well use TEXT() instead of T(), as it is dependent on UNICODE and not _UNICODE:

totalSpace.Format(TEXT("%I64d"), i64TotalGB);

In addition, this line is wrong as it tries to pass an ATL CString as a char* (a.k.a. C-style string):

printf("totalSpace contains: %s", totalSpace);

For which the compiler gives this warning:

warning C4477: 'printf' : format string '%s' requires an argument of type 'char *', but variadic argument 1 has type 'ATL::CString'

While the structure of CString is practically compatible with passing it like you have, this is still formally undefined behavior. Use CString::GetString() to safeguard against it:

printf("totalSpace contains: %ls", totalSpace.GetString());

Note the %ls as under my configuration totalSpace.GetString() returned a const wchar_t*. However, as "printf does not currently support output into a UNICODE stream.", the correct version for this line, that will support characters outside your current code page, is a call to wprintf() in the following manner:

wprintf("totalSpace contains: %s", totalSpace.GetString());

Having said ALL that, here's a general advice, regardless of the direct problem behind the question. The far better practice nowadays is slightly different altogether, and I quote from the respectable answer by @IInspectable, saying that "generic-text mappings were relevant 2 decades ago".

What's the alternative? In the absence of good enough reason, try sticking explicitly to CStringW (A Unicode character type string with CRT support). Prefer the L character literal over the archaic data/text mappings that depend on whether the constant _UNICODE or _MBCS has been defined in your program. Conversely, the better practice would be using the wide-character versions of all API and language library calls, such as wprintf() instead of printf().

Colcannon answered 25/9, 2018 at 9:37 Comment(1)
@Garin I am now seeing it in the docs: "printf does not currently support output into a UNICODE stream." -- made an edit to reflect. quite a detail but important enough to note IMHOColcannon
G
6

The bug is a result of numerous issues with the code, specifically these 2:

  • totalSpace.Format(_T("%I64d", i64TotalGB));

    This uses the _T macro in a way it's not meant to be used. It should wrap a single character string literal. In the code it wraps a second argument.

  • printf("totalSpace contains: %s", totalSpace);

    This assumes an ANSI-encoded character string, but passes a CString object, that can store both ANSI as well as Unicode encoded strings.

The recommended course of action is to drop generic-text mappings altogether, in favor of using Unicode (that's UTF-16LE on Windows) throughout1. The generic-text mappings were relevant 2 decades ago, to ease porting of Win9x code to the Windows NT based products.

To do this

  • Choose CStringW over CString.
  • Drop all occurences of _T, TEXT, and _TEXT, and replace them with an L prefix.
  • Use the wide-character version of the Windows API, CRT, and C++ Standard Library.

The fixed code looks like this:

__int64 i64TotalGB;
CStringW totalSpace;  // Use wide-character string
i64TotalGB = 150;
printf("disk space: %I64d GB\n", i64TotalGB);
totalSpace.Format(L"%I64d", i64TotalGB);  // Use wide-character string literal
wprintf(L"totalSpace contains: %s", totalSpace.GetString());  // Use wide-character library

On an unrelated note, while it is technically safe to pass a CString object in place of a character pointer in a variable argument list, this is an implementation detail, and not formally documented to work. Call CString::GetString() if you care about correct code.


1 Unless there is a justifiable reason to use a character encoding that uses char as its underlying type (like UTF-8 or ANSI). In that case you should still be explicit about it by using CStringA.

Garin answered 25/9, 2018 at 10:2 Comment(0)
G
2

try this

totalSpace.Format(_T("%I64d"), i64TotalGB);
Gondolier answered 25/9, 2018 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.