← All guides

Build errors

C4267 and C4244 in an MFC x64 Build: Fix size_t and ptrdiff_t Conversion Warnings

Published

An MFC project that has built without a single warning for years gets an x64 configuration. The first x64 build prints warnings like these, in lines you have not touched for a long time:

Report.cpp(25,13): warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data
Report.cpp(28,14): warning C4244: 'initializing': conversion from 'INT_PTR' to 'int', possible loss of data
Report.cpp(33,14): warning C4244: 'initializing': conversion from '__int64' to 'int', possible loss of data
Report.cpp(36,20): warning C4267: 'initializing': conversion from 'size_t' to 'DWORD', possible loss of data

The same code builds with no warnings as Win32. In x64, each of these lines puts a value that is 8 bytes wide into a 4-byte variable.

Why they appear only in x64

On x64, int and DWORD stay 4 bytes, while size_t, ptrdiff_t (the type of a pointer difference), and INT_PTR become 8 bytes. In Win32 all of them are 4 bytes, so nothing is narrowed and the compiler has nothing to report. In x64, an 8-byte value that is stored in a 4-byte variable can lose its upper half, and the compiler warns about that.

Which warning you get depends on the source type:

Source type Typical expression Warning
size_t std::vector::size(), _tcslen() C4267
INT_PTR CStringArray::GetSize(), CArray::GetSize() C4244
ptrdiff_t pEnd - pStart C4244

C4267 is reported only for size_t. A value of type INT_PTR or __int64 gives C4244 instead, so a C4244 on a GetSize() line is the same kind of problem as the C4267 on a size() line. Both are level 3 warnings (C4267, C4244).

Give the value its own type

The four lines behind the warnings above:

std::vector<CString> lines;
CStringArray errors;
// ... fill both ...

int nLines = lines.size();                                   // C4267

int nErrors = errors.GetSize();                              // C4244

LPCTSTR pszLine = lines[0];
LPCTSTR pszLevel = _tcsstr(pszLine, _T("INFO"));
int nColumn = pszLevel - pszLine;                            // C4244

DWORD cbFirstLine = (_tcslen(pszLine) + 1) * sizeof(TCHAR);  // C4267

For a count, an index, or a distance, the fix is to declare the variable with the type the expression already has:

size_t nLines = lines.size();
INT_PTR nErrors = errors.GetSize();
ptrdiff_t nColumn = pszLevel - pszLine;

Loop counters follow the same rule. for (size_t i = 0; i < lines.size(); ++i) for a std::vector, and for (INT_PTR i = 0; i < errors.GetSize(); ++i) for an MFC collection, so that the counter and the limit have the same type.

The format strings that print these variables have to change too. %d expects an int, so use %zu for size_t and %td for ptrdiff_t. For INT_PTR, cast to long long and use %lld:

sReport.Format(_T("Lines: %zu, errors: %lld, column: %td"),
	nLines, static_cast<long long>(nErrors), nColumn);

When an API requires a DWORD or an int

The fourth line is different. The value is a byte count that goes to an API with a DWORD parameter, and you cannot change the parameter. The value has to be narrowed, so check that it fits before you do, and handle the case where it does not:

static bool TrySizeToDword(size_t value, DWORD& result)
{
	if (value > MAXDWORD)
		return false;

	result = static_cast<DWORD>(value);
	return true;
}

DWORD cbFirstLine = 0;
if (!TrySizeToDword((_tcslen(pszLine) + 1) * sizeof(TCHAR), cbFirstLine))
	return _T("The first line is too long to pass as a DWORD.");

For an int parameter, compare against INT_MAX in the same way. A bare static_cast<DWORD>(...) also removes the warning, but a value above 4 GB is then cut to its lower 32 bits and passed on without any message. Do not use ASSERT for this check: it is gone from a Release build, and the Release build is where large data shows up. When the value can exceed the limit, return an error or show a message.

Do not silence the warning for the whole project

#pragma warning(disable: 4267) or a lower warning level stops the report for every line, including ones that really lose data later. Fix each line by type first. If one line has to stay narrow and you have checked the range, #pragma warning(suppress: 4267) on the line before it affects only that line.

References