Upcoming Events
Gen Con UK
8/28 - 8/31  

Penny Arcade Expo
8/29 - 8/31 @ Seattle, WA

Virtual Worlds Conference and Expo
9/3 - 9/4 @ Los Angeles, CA

Women In Games
9/10 - 9/12 @ Coventry, United Kingdom

More events...


Quick Stats
5009 people currently visiting GDNet.
2207 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

  search:   

20 issues of porting C++ code on the 64-bit platform


Contents
  Introduction
  Issues 1-4
  Issues 5-9
  Issues 10-14
  Issues 15-20
  Conclusion

  Printable version
  Discuss this article

1. Off-warnings

In all books devoted to the development of the quality code it is recommended to set a warning level of warnings shown by the compiler on as high level as possible. But there are situations in practice when for some project parts there is a lower diagnosis level set or it is even set off. Usually it is very old code which is supported but not modified. Programmers who work over the project are used to that this code works and don’t take its quality into consideration. Here it is a danger to miss serious warnings by the compiler while porting programs on the new 64-bit system.

While porting an application you should obligatory set on warnings for the whole project which help to check the compatibility of the code and analyze them thoroughly. It can help to save a lot of time while debugging the project on the new architecture.

If we won’t do this the simplest and stupidest errors will occur in all their variety. Here it is a simple example of overflow which occurs in a 64-bit program if we ignore warnings at all.

unsigned char *array[50];
unsigned char size = sizeof(array);
32-bit system: sizeof(array) = 200
64-bit system: sizeof(array) = 400

2. Use of the functions with a variable number of arguments

The typical example is the incorrect use of printf, scanf functions and their variants:

1) const char *invalidFormat = "%u";
   size_t value = SIZE_MAX;
   printf(invalidFormat, value);
2) char buf[9];
   sprintf(buf, "%p", pointer);
In the first case it is not taken into account that size_t type is not equivalent to unsigned type on the 64-bit platform. It will cause the printing of an incorrect result in case if value > UINT_MAX.

In the second case the author of the code didn’t take into account that the pointer size may become more than 32-bit in future. As a result this code will cause the buffer overflow on the 64-bit architecture.

The incorrect use of functions with a variable number of arguments is a typical error on all the architectures, not only on 64-bit ones. This is related to the fundamental danger of the use of the given C++ language constructions. The common practice is to refuse them and use safe programming methods. We recommend you strongly to modify the code and use safe methods. For example, you may replace printf with cout, and sprintf with boost::format or std::stringstream.

If you have to support the code which uses functions of scanf type, in the control lines format we can use special macros which open into necessary modifiers for different systems. An example:

// PR_SIZET on Win64 = "I"
// PR_SIZET on Win32 = ""
// PR_SIZET on Linux64 = "l"
// ...
size_t u;
scanf("%" PR_SIZET "u", &u);

3. Magic numbers

In the low-quality code there are often magic numbers the mere presence of which is dangerous. During the migration of the code on the 64-bit platform these magic numbers may make the code inefficient if they participate in operations of calculation of address, objects size or bit operations.

Table N3 contains basic magic numbers which may influence the workability of an application on a new platform.

Value Description
4 Number of bytes in a pointer type
32 Number of bits in a pointer type
0x7fffffff The maximum value of a 32-bit signed variable. Mask for zeroing of the high bit in a 32-bit type.
0x80000000 The minimum value of a 32-bit signed variable. Mask for allocation of the high bit in a 32-bit type.
0xffffffff The maximum value of a 32-bit variable. An alternative record -1 as an error sign.
Table N3. Basic magic numbers which can be dangerous during the port of applications from 32-bit platform on the 64-bit one.

You should study the code thoroughly in search of magic numbers and replace them with safe numbers and expressions. To do so you can use sizeof() operator, special values from , etc.

Let’s look at some errors related to the use of magic numbers. The most frequent is a record of number values of type sizes.

1) size_t ArraySize = N * 4;
   intptr_t *Array = (intptr_t *)malloc(ArraySize);
2) size_t values[ARRAY_SIZE];
   memset(values, ARRAY_SIZE * 4, 0);
3) size_t n, newexp;
   n = n >> (32 - newexp);
Let’s suppose that in all cases the size of the types used is always 4 bytes. The correction of the code is in the use of sizeof() operator.
1) size_t ArraySize = N * sizeof(intptr_t);
   intptr_t *Array = (intptr_t *)malloc(ArraySize);
2) size_t values[ARRAY_SIZE];
   memset(values, ARRAY_SIZE * sizeof(size_t), 0);
or
   memset(values, sizeof(values), 0); //preferred alternative
3) size_t n, newexp;
   n = n >> (CHAR_BIT * sizeof(n) - newexp); 
Sometimes we may need a specific number. As an example let’s take the size_t where all the bits except 4 low bits must be filled with ones. In a 32-bit program this number may be declared in the following way.
// constant '1111..110000'
const size_t M = 0xFFFFFFF0u;
This is the incorrect code in the case of the 64-bit system. Such errors are very unpleasant because the record of magic numbers may be carried out in different ways and the search for them is very laborious. Unfortunately, there is no other way except to find and to correct this code using #ifdef or a special macro.
#ifdef _WIN64
  #define CONST3264(a) (a##i64)
#else
  #define CONST3264(a)  (a)
#endif
const size_t M = ~CONST3264(0xFu);
Sometimes as an error code or other special marker "-1" value is used which is written as "0xffffffff". On the 64-bit platform the recorded expression is incorrect and we should evidently use -1 value. An example of the incorrect code using 0xffffffff value as an error sign.
#define INVALID_RESULT (0xFFFFFFFFu)
size_t MyStrLen(const char *str) {
  if (str == NULL)
    return INVALID_RESULT;
  ...
  return n;
}
size_t len = MyStrLen(str);
if (len == (size_t)(-1))
  ShowError();
To be on the safe side, let’s make sure that you know clearly what is the result of "(size_t)(-1)" value on the 64-bit platform. You may make a mistake saying value 0x00000000FFFFFFFFu. According to C++ rules -1 value turns into a signed equivalent of a higher type and then into an unsigned value:
int a = -1;           // 0xFFFFFFFFi32
ptrdiff_t b = a;      // 0xFFFFFFFFFFFFFFFi64
size_t c = size_t(b); // 0xFFFFFFFFFFFFFFFui64
Thus "(size_t)(-1)" on the 64-bit architecture is represented by 0xFFFFFFFFFFFFFFFui64 value which is the highest value for the 64-bit size_t type.

Let’s return to the error with INVALID_RESULT. The use of the number 0xFFFFFFFFu causes the failure of the execution of "len == (size_t)(-1)" condition in a 64-bit program. The best solution is to change the code in such a way that it doesn’t need special marker values. If you cannot refuse them for some reason or think it unreasonable to correct the code fundamentally just use fair value -1.

#define INVALID_RESULT (size_t(-1))
...

4. Storing of integers in double type

Double type as a rule has size 64 bits and is compatible with IEEE-754 standard on 32-bit and 64-bit systems. Some programmers use double type for storing of and work with integer types.

size_t a = size_t(-1);
double b = a;
--a;
--b;
size_t c = b; // x86: a == c
              // x64: a != c
The given example can be justified on a 32-bit system for double type has 52 significant bits and is capable to store a 32-bit integer value without a loss. But while trying to store a 64-bit integer in double the exact value can be lost (see picture 1).


Picture 1. The number of significant bits in size_t and double types.

It is possible that an approximate value can be used in your program, but to be on the safe side we’d like to warn you about possible effects on the new architecture. And in any case it is not recommended to mix integer arithmetic with floating-point arithmetic.



Issues 5-9