>
>
>
Some examples of the 64-bit code errors

Evgenii Ryzhkov
Articles: 125

Some examples of the 64-bit code errors

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.

Introduction

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:

  • int and long remained 32-bit ones with 64-bit versions of Widows;
  • size_t, time_t, and ptrdiff_t became 64-bit ones with 64-bit versions of Windows.

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/ .

An error source code example

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.

Implicit conversion to memsize type

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.

The use of non-memsize types for the pointers arithmetic

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.

Implicit conversion of memsize type

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).

Implicit conversion to memsize type in an arithmetic sentence

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.

Implicit conversion to memsize type in ?: operation

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).

Implicit conversion of a function argument to memsize type

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.

Implicit conversion of a function argument of memsize type to the 32-bit type

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.

The use of incorrect types for indexation

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.

Conversion to memsize type using a return value

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.

Conversion of memsize type using a return value

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.

Call of function with variable number of arguments with memsize parameter

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.

Implicit conversion of memsize type to double and vice versa

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.

Explicit type conversion if the course of work with pointers

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.

Overriding virtual functions

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.

Pro conclusion

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.

Some information about the author

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.