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
5069 people currently visiting GDNet.
2208 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

15. Implicit type conversions while using functions

Observing the previous kind of errors related to mixing of simple integer types and memsize types, we surveyed only simple expressions. But similar problems may occur while using other C++ constructions too. extern int Width, Height, Depth; size_t GetIndex(int x, int y, int z) { return x + y * Width + z * Width * Height; } ... MyArray[GetIndex(x, y, z)] = 0.0f; In case if you work with large arrays (more than INT_MAX items) the given code may behave incorrectly and we’ll address not those items of the array MyArray we wanted. In spite the fact that we return the value of size_t type "x + y * Width + z * Width * Height" expression is calculated with the use of int type. We suppose you have already guessed that the corrected code will look as follows:

extern int Width, Height, Depth;
size_t GetIndex(int x, int y, int z) {
  return (size_t)(x) +
         (size_t)(y) * (size_t)(Width) +
         (size_t)(z) * (size_t)(Width) * (size_t)(Height);
}
In the next example we also have memsize type (pointer) and simple unsigned type mixed.
extern char *begin, *end;
unsigned GetSize() {
  return end - begin;
}
The result of "end - begin" expression have ptrdiff_t type. As far as the function returns unsigned type the implicit type conversion occurs during which high bits of the results get lost. Thus if pointers begin and end address the beginning and the end of the array according to the larger UINT_MAX (4Gb), the function will return the incorrect value.

And here it is one more example but now we’ll observe not the returned value but the formal function argument.

void foo(ptrdiff_t delta);
int i = -2;
unsigned k = 1;
foo(i + k);
Does not this code remind you of the example of the incorrect pointer arithmetic discussed earlier? Yes, we find the same situation here. The incorrect result appears during the implicit type conversion of the actual argument which has value 0xFFFFFFFF and from unsigned type to ptrdiff_t type.

16. Overload functions

During the port of 32-bit programs on the 64-bit platform the change of the logic of its work may be found which is related to the use of overload functions. If the function is overlapped for 32-bit and 64-bit values the access to it with the argument of memsize type will be compiled into different calls on different systems. This method may be useful as, for example, in the following code:

static size_t GetBitCount(const unsigned __int32 &) {
  return 32;
}
static size_t GetBitCount(const unsigned __int64 &) {
  return 64;
}
size_t a;
size_t bitCount = GetBitCount(a);
But such a change of logic has a potential danger. Imagine a program in which a class for organizing stack is used for some aims. The peculiarity of this class is that it allows to store value of different types.
class MyStack {
...
public:
  void Push(__int32 &);
  void Push(__int64 &);
  void Pop(__int32 &);
  void Pop(__int64 &);
} stack;
ptrdiff_t value_1;
stack.Push(value_1);
...
int value_2;
stack.Pop(value_2);
A careless programmer placed and then chose form the stack of values of different types (ptrdiff_t and int). On the 32-bit system their sizes coincided and everything worked perfectly. When the size of ptrdiff_t type changes in a 64-bit program stack began to include more bytes than it extract out later.

We think you understand this kind of errors and that you should pay attention to the call of overload functions transferring actual arguments of memsize type.

17. Data alignment

Processors work more efficiently when they deal with data aligned properly. As a rule the 32-bit data item must be aligned at the border multiple 4 bytes and the 64-bit item at the border 8 bytes. The try to work with unaligned data on processors IA-64 (Itanium) as it is shown in the following example, will cause exception. #pragma pack (1) // Also set by key /Zp in MSVC struct AlignSample { unsigned size; void *pointer; } object; void foo(void *p) { object.pointer = p; // Alignment fault } If you have to work with unaligned data on Itanium you should indicate this to the compiler. For example, you may use a special macro UNALIGNED:

#pragma pack (1) // Also set by key /Zp in MSVC
struct AlignSample {
  unsigned size;
  void *pointer;
} object;
void foo(void *p) {
  *(UNALIGNED void *)&object.pointer = p; //Very slow
}
This decision is not efficient for the access to the unaligned data will be several times slower. A better result may be achieved when you arrange up to 32-bit, 16-bit and 8-bit items in 64-bit data items.

On the architecture x64 during the access to unaligned data exception does not occur but you should avoid them either. Firstly, because of the essential slowing down of the speed of the access to these data, and secondly, because of a high probability of porting the program on the platform IA-64 in future.

Let’s look at one more example of the code which does not take into account the data alignment.

struct MyPointersArray {
  DWORD m_n;
  PVOID m_arr[1];
} object;
...
malloc( sizeof(DWORD) + 5 * sizeof(PVOID) );
...
If we want to allocate the memory size necessary for storing of the object of MyPointersArray type containing 5 pointers, we should take into account that the beginning of the array m_arr will be aligned at the border of 8 bytes. The order of the data in memory on different systems (Win32/Win64) is shown on picture 7.


Picture 7. Alignment of the data in memory on systems Win32 and Win64

The correct calculation of the size should look as follows:

struct MyPointersArray {
  DWORD m_n;
  PVOID m_arr[1];
} object;
...
malloc( FIELD_OFFSET(struct MyPointersArray, m_arr) +
        5 * sizeof(PVOID) );
...
In this code we see the shift of the last structure member and sum up this shift and its size. The shift of a member of the structure or a class may be recognized when macro offsetof or FIELD_OFFSET is used.

Always use these macros to get a shift in the structure without relying on your knowledge of the sizes of types and the alignment. Here it is the example of the code with the correct calculation of the structure member address:

struct TFoo {
  DWORD_PTR whatever;
  int value;
} object;
int *valuePtr = 
  (int *)((size_t)(&object) + offsetof(TFoo, value)); // OK

18. Exceptions

Catch and handle exceptions when integer types participate is not a good programming practice while working in C++ language. For such aims you should use more informative types, for example classes derived from class std::exception. But sometimes one has to work with less quality code as it is shown further.

char *ptr1;
char *ptr2;
try {
  try {
    throw ptr2 - ptr1;
  }
  catch (int) {
    std::cout << "catch 1: on x86" << std::endl;
  }
}
catch (ptrdiff_t) {
  std::cout << "catch 2: on x64" << std::endl;
}
You should thoroughly avoid the catch or the handle exceptions with the use of memsize types for it may cause the change of the logic of program work. The correction of the given code may consist in the replacement of "catch (int)" with "catch (ptrdiff_t)". A more proper correction will be the use of a special class for transferring the information about the error which took place.

19. The use of outdated functions and predefined constants

While developing a 64-bit application, be sure to bear in mind the changes of the environment in which it will be performed. Some functions will become outdated and it will be necessary to replace them with some variants. GetWindowLong is a good example of such function in the Windows operation system. Pay your attention to the constants referring to the interaction with the environment in which the program is functioning. In Windows the lines containing "system32" or "Program Files" will be suspect.

20. Explicit type conversions

Be accurate with explicit type conversions. They may change the logic of the program execution when types change their capacity of cause the loss of significant bits. It is difficult to adduce typical examples of errors related to the explicit type conversion for they are very different and specific for different programs. You have become acquainted with some errors related to the explicit type conversion earlier.



Conclusion

`