Webinar: Evaluation - 05.12
While porting 32-bit software to 64-bit systems there may appear some errors in the code of applications which were written in C++ language. The cause for these hides in the alteration of the base data types (to be more exact, in the relations between them) with the new hardware platform.
This article contains various examples of 64-bit errors. However, we have learnt much more examples and types of errors since we started writing the article and they were not included into it. Please see the article "A Collection of Examples of 64-bit Errors in Real Programs" that covers defects in 64-bit programs we know of most thoroughly. We also recommend you to study the course "Lessons on development of 64-bit C/C++ applications" where we describe the methodology of creating correct 64-bit code and searching for all types of defects using the Viva64 code analyzer.
While porting 32-bit software to 64-bit systems there may appear some errors in the code of applications which were written in C++ language. The cause for these hides in the alteration of the base data types (to be more exact, in the relations between them) with the new hardware platform. "But isn't C++ a high-level language!" you may ask, and you will be right. But still all the high-level abstractions are realized through the low-level data types.
Help documentation for developers is sure to contain the description of such errors. However, even such authoritative sources as, for example MSDN, often give only platitudes, for instance:
But what does it mean for a developer and what problems may it potentially cause - all this is not reported in the help.
Meanwhile, there are very few articles which contain certain examples of application code errors in 64-bit Windows versions. This article is to fill the vacuum.
First of all some terminology. Memsize type is any data type which changes its size when the digit capacity of architecture is changed from 32 bits to 64 bits. The examples are size_t, ptrdiff_t, DWORD_PTR, LONG_PTR and others.
Take a note that only short examples of errors are given in the article. The explanation of their causes are given in the article "20 issues of porting C++ of porting C++ code on the 64-bit platform"http://www.viva64.com/en/a/0004/ .
Let us not harass the developers who wish to get down to studying the error examples, so let's show the whole source code of such a program. After the source code each error will be considered separately.
To demonstrate the errors it is necessary to compile and run this code in the 64-bit mode.
You can find the source code of an application which contains this code in a Viva64 distributive named PortSample. For this purpose download and install Viva64 and then install PortSamle from program folder Viva64.
bool IsX64Platform() {
return sizeof(size_t) == 8;
}
template <typename A, typename B>
inline size_t SafeMul(A a, B b) {
return static_cast<size_t>(a) * static_cast<size_t>(b);
}
template <typename A, typename B, typename C>
inline size_t SafeMul(A a, B b, C c) {
return static_cast<size_t>(a) * static_cast<size_t>(b) *
static_cast<size_t>(c);
}
template <typename A, typename B, typename C, typename D>
inline size_t SafeMul(A a, B b, C c, D d) {
return static_cast<size_t>(a) * static_cast<size_t>(b) *
static_cast<size_t>(c) * static_cast<size_t>(d);
}
void V101()
{
unsigned imageWidth = 1000;
unsigned imageHeght = 1000;
unsigned bytePerPixel = 3;
unsigned maxFrameCountInBuffer;
if (IsX64Platform()) {
maxFrameCountInBuffer = 2000;
} else {
maxFrameCountInBuffer = 100;
}
size_t bufferSize = imageWidth * imageHeght *
bytePerPixel * maxFrameCountInBuffer;
BYTE *buffer = static_cast<BYTE *>(malloc(bufferSize));
BYTE *ptr = buffer;
for (unsigned frame = 0; frame != maxFrameCountInBuffer; ++frame)
for (unsigned width = 0; width != imageWidth; ++width)
for (unsigned height = 0; height != imageHeght; ++height) {
*ptr++ = 0xFF;
*ptr++ = 0xFF;
*ptr++ = 0x00;
}
free (buffer);
}
void V102()
{
int domainWidth;
int domainHeght;
int domainDepth;
if (IsX64Platform()) {
domainWidth = 2000;
domainHeght = 2000;
domainDepth = 2000;
} else {
domainWidth = 500;
domainHeght = 500;
domainDepth = 500;
}
char *buffer =
new char [size_t(domainWidth) * size_t(domainHeght) *
size_t(domainDepth)];
char *current = buffer;
char *end = buffer;
end += domainWidth * domainHeght * domainDepth;
while (current != end)
*current++ = 1;
delete [] buffer;
}
void V103()
{
size_t Megabyte = 1048576;
size_t Gigabyte = 1073741824;
size_t n = IsX64Platform() ? Gigabyte : Megabyte;
unsigned arraySize = n * sizeof(INT_PTR);
INT_PTR *buffer = (INT_PTR *)malloc(size_t(arraySize));
for (size_t i = 0; i != n; ++i)
buffer[i] = 0;
free(buffer);
}
void V104()
{
volatile size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *buffer = new char [n];
volatile size_t index = 0;
volatile unsigned i;
for (i = 0; i != n; ++i)
buffer[index++] = 1;
delete [] buffer;
}
void V105()
{
bool flag = true;
unsigned a = unsigned(-1);
if ((flag ? a : sizeof(float)) != size_t(-1)) {
throw CString("x64 portability issues");
}
}
void V106()
{
void *buffer;
const unsigned Megabyte = 1024 * 1024;
const unsigned Gigabyte = 1024 * 1024 * 1024;
unsigned unit;
if (IsX64Platform())
unit = Gigabyte;
else
unit = Megabyte;
buffer = malloc(5 * unit);
if (IsX64Platform())
memset(buffer, 0, SafeMul(5, 1024, 1024, 1024));
else
memset(buffer, 0, SafeMul(5, 1024, 1024));
free(buffer);
}
void V107_FillFunction(char *array, unsigned arraySize) {
for (unsigned i = 0; i != arraySize; ++i)
array[i] = 1;
}
void V107()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
V107_FillFunction(array, n);
for (size_t i = 0; i != n; ++i)
if (array[i] != 1)
throw CString("x64 portability issues");
free(array);
}
void V108()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
volatile int index = 0;
for (size_t i = 0; i != n; ++i) {
array[index++] = 1;
if (array[i] != 1)
throw CString("x64 portability issues");
}
free(array);
}
ptrdiff_t UnsafeCalcIndex(int x, int y, int width) {
volatile int result = x + y * width;
return result;
}
void V109()
{
int domainWidth;
int domainHeght;
if (IsX64Platform()) {
domainWidth = 50000;
domainHeght = 50000;
} else {
domainWidth = 5000;
domainHeght = 5000;
}
char *array = (char *)malloc(SafeMul(domainWidth, domainHeght));
for (int x = 0; x != domainWidth; ++x)
for (int y = 0; y != domainHeght; ++y) {
array[UnsafeCalcIndex(x, y, domainWidth)] = 55;
}
free(array);
}
int UnsafeStrLen(const char *text) {
const char *ptr = text;
while (*ptr != 0)
++ptr;
return ptr - text;
}
void V110()
{
size_t n;
CString trueSize;
if (IsX64Platform()) {
n = SafeMul(3, 1024, 1024, 1024);
trueSize = _T("3221225472");
} else {
n = SafeMul(3, 1024, 1024);
trueSize = _T("3145728");
}
char *str = (char *)malloc(n * sizeof(char));
memset(str, 'V', n * sizeof(char));
str[n - 1] = 0;
int len = UnsafeStrLen(str);
CString falseSize;
falseSize.Format(_T("%i"), len + 1);
free(str);
if (falseSize != trueSize)
throw CString(_T("x64 portability issues"));
}
void V111()
{
char invalidStr[100], validStr[100];
const char *invalidFormat = "%u";
const char *validFormat = "%Iu";
size_t a = SIZE_MAX;
sprintf_s(invalidStr, sizeof(invalidStr),invalidFormat, a);
sprintf_s(validStr, sizeof(validStr), validFormat, a);
if (strcmp(invalidStr, validStr) != 0)
throw CString(_T("x64 portability issues"));
}
void V113()
{
size_t a = size_t(-1);
double b = a;
--a;
--b;
size_t c = b;
if (a != c)
throw CString(_T("x64 portability issues"));
}
void V114()
{
unsigned intPtr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
size_t *sizetPtr = (size_t *)(intPtr);
size_t sum = 0;
for (size_t i = 0; i != 10; ++i)
sum += sizetPtr[i];
if (sum != 45)
throw CString(_T("x64 portability issues"));
}
void V301()
{
class CWinAppTest {
public:
virtual void WinHelp(DWORD_PTR, UINT) {
::AfxMessageBox(_T("Cannot activate WinHelp"));
}
};
class CPortSampleApp : public CWinAppTest {
public:
virtual void WinHelp(DWORD, UINT) {
::AfxMessageBox(_T("WinHelp activated"));
}
};
CWinAppTest *Application = new CPortSampleApp();
Application->WinHelp(NULL, 0);
delete Application;
}
int _tmain(int argc, TCHAR* argv[])
{
V101();
V102();
V103();
V104();
V105();
V106();
V107();
V108();
V109();
V110();
V111();
V112();
V113();
V114();
V201();
V202();
V203();
V301();
return 0;
}
Now, when we see the whole code, let's consider the functions which contain errors. When we say that a function contains an error we mean the following: the given code is able to compile and function in the 32-bit regime, but after compiling for the64-bit regime its functioning becomes incorrect up to fall.
void V101()
{
unsigned imageWidth = 1000;
unsigned imageHeght = 1000;
unsigned bytePerPixel = 3;
unsigned maxFrameCountInBuffer;
if (IsX64Platform()) {
maxFrameCountInBuffer = 2000;
} else {
maxFrameCountInBuffer = 100;
}
size_t bufferSize = imageWidth * imageHeght *
bytePerPixel * maxFrameCountInBuffer;
BYTE *buffer = static_cast<BYTE *>(malloc(bufferSize));
BYTE *ptr = buffer;
for (unsigned frame = 0; frame != maxFrameCountInBuffer; ++frame)
for (unsigned width = 0; width != imageWidth; ++width)
for (unsigned height = 0; height != imageHeght; ++height) {
*ptr++ = 0xFF;
*ptr++ = 0xFF;
*ptr++ = 0x00;
}
free (buffer);
}
The problem is in the next line:
size_t bufferSize = imageWidth * imageHeght *
bytePerPixel * maxFrameCountInBuffer;
All the varibles in the multiplation are of unsigned type, which in both 32-bit and 64-bit regimes pssesses the sie of 32 bits. But the result of multiplation is written with a variable of size_t type which in the 32-bit mode possesses the size coinciding with the size of unsigned type and they don't coincide in the 64-bit mode. But the compiler fulfills the extention of the result type up to unsigned one.It seems that there is no problem at all. But the problem exists! If the result of multplication exceeds 4 gigabytes the overflow will occur and the result will be incorrect.
void V102()
{
int domainWidth;
int domainHeght;
int domainDepth;
if (IsX64Platform()) {
domainWidth = 2000;
domainHeght = 2000;
domainDepth = 2000;
} else {
domainWidth = 500;
domainHeght = 500;
domainDepth = 500;
}
char *buffer =
new char [size_t(domainWidth) * size_t(domainHeght) *
size_t(domainDepth)];
char *current = buffer;
char *end = buffer;
end += domainWidth * domainHeght * domainDepth;
while (current != end)
*current++ = 1;
delete [] buffer;
}
The problem in the given code is the pointers arithmetic, to be more exact, the use of non-memsize types for this arithmetic:
end += domainWidth * domainHeght * domainDepth;
The error is that with the 64-bit platform the pointer end will never have the increment larger than 4 gigabytes.
void V103()
{
size_t Megabyte = 1048576;
size_t Gigabyte = 1073741824;
size_t n = IsX64Platform() ? Gigabyte : Megabyte;
unsigned arraySize = n * sizeof(INT_PTR);
INT_PTR *buffer = (INT_PTR *)malloc(size_t(arraySize));
for (size_t i = 0; i != n; ++i)
buffer[i] = 0;
free(buffer);
}
There is an obvious error in the following code fragment.
unsigned arraySize = n * sizeof(INT_PTR);
It's the implicit conversion to the unsigned type of a variable of larger capacity (on a 64-bit platform).
void V104()
{
volatile size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *buffer = new char [n];
volatile size_t index = 0;
volatile unsigned i;
for (i = 0; i != n; ++i)
buffer[index++] = 1;
delete [] buffer;
}
It's strange but operations of comparing two variables may be also the source of trouble. In the following line
for (i = 0; i != n; ++i)
the problem is that the variable i of unsigned type is compared to the variable n of size_t type, and after that this variable extends. But as unsigned never exceeds 4 gigabytes , than i will never be larger than this value. What do we have as a result? We have an infinite loop! as the conditions of i != n will always be fulfilled.
void V105()
{
bool flag = true;
unsigned a = unsigned(-1);
if ((flag ? a : sizeof(float)) != size_t(-1)) {
throw CString("x64 portability issues");
}
}
This example is very much alike to the previous one, the problem can be found in the following line:
if ((flag ? a : sizeof(float)) != size_t(-1)) {
here the variable a is of unsigned type which may give an incorrect result when compared to size_t. Why? Just because unsigned(-1) is not equal to size_t (-1).
void V106()
{
void *buffer;
const unsigned Megabyte = 1024 * 1024;
const unsigned Gigabyte = 1024 * 1024 * 1024;
unsigned unit;
if (IsX64Platform())
unit = Gigabyte;
else
unit = Megabyte;
buffer = malloc(5 * unit);
if (IsX64Platform())
memset(buffer, 0, SafeMul(5, 1024, 1024, 1024));
else
memset(buffer, 0, SafeMul(5, 1024, 1024));
free(buffer);
}
In the line
buffer = malloc(5 * unit);
the developer hoped to get a 5-gigabyte buffer with a 64-bit system. But an error will occur here. You ask why? Just because the malloc() function possesses an argument of memsize type and 5 is quite an appropriate value. But when (5 * unit) is multiplied an overflow will occur because the unit variable is of unsigned type. The result will for sure be not 5 gigabytes.
void V107_FillFunction(char *array, unsigned arraySize) {
for (unsigned i = 0; i != arraySize; ++i)
array[i] = 1;
}
void V107()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
V107_FillFunction(array, n);
for (size_t i = 0; i != n; ++i)
if (array[i] != 1)
throw CString("x64 portability issues");
free(array);
}
In the line with function call
V107_FillFunction(array, n);
there occurs the conversion of type of the variable n to unsigned. This means truncation of the variable value, the result of this is that not the whole array is filled.
void V108()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
volatile int index = 0;
for (size_t i = 0; i != n; ++i) {
array[index++] = 1;
if (array[i] != 1)
throw CString("x64 portability issues");
}
free(array);
}
If not a memsize type is used for array indexation, it is possible that there will occur an error like the following:
array[index++] = 1;
The problem is the following: in case if there are more than 4 gigabytes of elements , you may not use the variable of unsigned type.
ptrdiff_t UnsafeCalcIndex(int x, int y, int width) {
volatile int result = x + y * width;
return result;
}
void V109()
{
int domainWidth;
int domainHeght;
if (IsX64Platform()) {
domainWidth = 50000;
domainHeght = 50000;
} else {
domainWidth = 5000;
domainHeght = 5000;
}
char *array = (char *)malloc(SafeMul(domainWidth, domainHeght));
for (int x = 0; x != domainWidth; ++x)
for (int y = 0; y != domainHeght; ++y) {
array[UnsafeCalcIndex(x, y, domainWidth)] = 55;
}
free(array);
}
It's amazing, but in this example the error is in the line:
return result;
The value result is of int type which will be implicitly expanded to ptrdiff_t. But the function UnsafeCalcIndex() will never be able to return the index of the element following 2 gigabytes. It would be more correct to say that the error is the wrongly chosen type of the variable result. In this case this variable must be of UnsafeCalcIndex() type.
int UnsafeStrLen(const char *text) {
const char *ptr = text;
while (*ptr != 0)
++ptr;
return ptr - text;
}
void V110()
{
size_t n;
CString trueSize;
if (IsX64Platform()) {
n = SafeMul(3, 1024, 1024, 1024);
trueSize = _T("3221225472");
} else {
n = SafeMul(3, 1024, 1024);
trueSize = _T("3145728");
}
char *str = (char *)malloc(n * sizeof(char));
memset(str, 'V', n * sizeof(char));
str[n - 1] = 0;
int len = UnsafeStrLen(str);
CString falseSize;
falseSize.Format(_T("%i"), len + 1);
if (falseSize != trueSize)
throw CString(_T("x64 portability issues"));
}
The situation is the same as in the previous example and the error is again in the line of the return value:
return ptr - text;
The difference is that here we deal with the conversion of memsize type to int type. The result is that the buffer size (from the example) will never be figured out if it is larger than 2 gigabytes.
void V111()
{
char invalidStr[100], validStr[100];
const char *invalidFormat = "%u";
const char *validFormat = "%Iu";
size_t a = SIZE_MAX;
sprintf_s(invalidStr, sizeof(invalidStr),invalidFormat, a);
sprintf_s(validStr, sizeof(validStr), validFormat, a);
if (strcmp(invalidStr, validStr) != 0)
throw CString(_T("x64 portability issues"));
}
Functions with variable number of arguments are often used for formatting and input/output of text lines. Incorrect presetting of the format line may cause incorrect work.
const char *invalidFormat = "%u";
sprintf_s(invalidStr, sizeof(invalidStr),invalidFormat, a);
The format line in this example is estimated for 32-bit mode of working and in 64-bit mode it will cause incorrect output.
void V113()
{
size_t a = size_t(-1);
double b = a;
--a;
--b;
size_t c = b;
if (a != c)
throw CString(_T("x64 portability issues"));
}
In this example there are errors in two lines:
double b = a;
and
size_t c = b;
Such asignment with 64-bit systems is incorrect because it may cause loss of preision.
void V114()
{
unsigned intPtr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
size_t *sizetPtr = (size_t *)(intPtr);
size_t sum = 0;
for (size_t i = 0; i != 10; ++i)
sum += sizetPtr[i];
if (sum != 45)
throw CString(_T("x64 portability issues"));
}
C++ being a low-level language allows working with memory at the pointer level. Explicit type conversion using pointers is anyway dangerous, but conversion of memsize types , as shown in the example, is twice dangerous.
size_t *sizetPtr = (size_t *)(intPtr);
The matter is the difference of types size_t and unsigned.
void V301()
{
class CWinAppTest {
public:
virtual void WinHelp(DWORD_PTR, UINT) {
::AfxMessageBox(_T("Cannot activate WinHelp"));
}
};
class CPortSampleApp : public CWinAppTest {
public:
virtual void WinHelp(DWORD, UINT) {
::AfxMessageBox(_T("WinHelp activated"));
}
};
CWinAppTest *Application = new CPortSampleApp();
Application->WinHelp(NULL, 0);
delete Application;
}
One of the funniest errors of C++ applications which can appear with 64-bit systems is related to virtual functions. Pay your attention to the parameters of virtual functions in the example above. With a 32-bit system DWORD_PTR and DWORD coincide and there appears an overridden virtual function, and with a 64-bit platform there are two different functions! As a result the call of WinHelp() function from the example will cause the appearance of "Cannot activate WinHelp" message.
Thus, we have listed all the main code errors which appear when a code is ported to 64-bit systems. You might think many of them are sophisticated. For example, who, for God's sake, may need a 5 gigabyte buffer at Windows system? Maybe this problem is not very acute in 2007, though many resource-intensive applications are already able to use such amount of memory. We'll see if this article will be actual in a couple of years. Maybe just you will debug an error which appears when several gigabytes of memory are allocated.
Evgeniy Ryzhkov is one of the developers of the static code analyzer Viva64 which is meant for simplifying the migration of applications to 64-bit platforms. He studies the migration of 32-bit program systems to 64-bit platforms.
0