Rules and Standards |
CAA V5 C++ 64-bit Operating System SupportRules, hints, and tips to help your applications run on 64-bit operating systems |
|
Technical Article |
AbstractCATIA, DELMIA, and DMU solutions run on the AIX and Windows operating system in 64-bit mode. If you are interested with 64-bit, you can check your application to build and run using the AIX 64-bit operating system. This article describes the 64-bit operating system characteristics and their consequences on you current application code. It also gives some tips and rules about compiling, reviewing, and debugging your code, along with the V5 generic basic types that help your code run on both 32-bit and 64-bit operating systems. Finally, some common errors found when running in 64-bit, but coming from 32-bit coding, are explained along with their solutions. |
A 64-bit operating system uses memory addressed using 64 bits instead of 32, that is, theoretically 16 EB (or 264 bytes, 1 exabyte being 260 bytes) instead of 4 GB (or 232 bytes, 1 gigabyte being 230 bytes). This increased virtual address space enables you:
Because of hardware or software restrictions, some platforms might not support the full 64-bit virtual address space. The actual limit is then below 16 EB, and is usually measured in TB (terabytes or 240 bytes). For example, Microsoft announces that 64-bit Windows NT supports 16 TB (or 244 bytes, 1 terabyte being 240 bytes) of memory, that is 1024 times more than 32-bit Windows NT.
All the components involved in a 64-bit application must be native 64-bit.
With AIX, an application built:
With Windows, an application built:
Unix and Windows both tried to minimize changes needed in their existing source code, which resulted in different definitions of some basic types. In particular, long was defined as a 64-bit type on Unix and as a 32-bit type on Windows.
The main changes are the size of some basic types:
Type | 32-bit (*) | 64-bit Unix | 64-bit Windows | Definition |
---|---|---|---|---|
int | 32 | 32 | 32 | Integer |
long | 32 | 64 | 32 | Long integer |
void * | 32 | 64 | 64 | Pointer |
size_t | 32 | 64 | 64 | Data type for
objects declared to store result of sizeof operator. |
ssize_t | 32 | 64 | 64 | Data type
used to represent the sizes of blocks that can be read or written in a
single operation. Similar to size_t , but must be signed |
off_t | 32 | 64 | 32 | Arithmetic data type used to represent file sizes |
time_t | 32 | 32 | 64 | Arithmetic data type representing calendar time |
(*): Common to Windows and UNIX.
All the components involved in a 64-bit application must be native 64-bit.
To enable your applications to go on running with 32-bit operating systems, the advice in this article rely on the following migration hypotheses:
Note: Having a very high automatic test code coverage is very helpful for 64-bit migration. Thus, once build time is successful, you will be able to estimate the time needed for run time convergence. But the most important is that you will be able to check that you do not introduce any regression either in 32-bit or in 64-bit.
To get ready for 64-bit, you should:
Once 64-bit pure convergence has been achieved, validate that 64-bit streamed documents are readable in 32-bit mode.
You can refer to [1] and [2] for additional information about Windows 64-bit and porting applications to 64-bit.
The mkmk environment uses the
_MkmkOS_BitMode
environment variable set to 64 to compile and build
code in 64-bit. On a 64-bit machine, this variable is set by tck_profile
automatically.
The 64-bit AIX and Windows operating system related codes in the directory are:
IBM AIX | aix_a64 |
Microsoft Windows | win_b64 |
To build your frameworks in 64-bit, type:
. tck_init tck_profile MyTCK adl_ch_ws MyWorkspace mkmk -auw
t:tck_init // assuming the RADE tool installation is mapped to the t: drive tck_profile MyTCK adl_ch_ws MyWorkspace mkmk -auw
In the build log, examine the errors or warnings that show 64-bit problems.
Some preprocessor flags are available to discriminate between the different types of processors or operating systems:
_WINDOWS_SOURCE |
To compile for Microsoft Windows |
_AIX_SOURCE |
To compile for IBM AIX |
_HPUX_SOURCE |
To compile for HP HP-UX |
_SOLARIS_SOURCE |
To compile for Sun Solaris |
PLATEFORME_DS64 |
To compile in native 64-bit mode. Valid with all operating systems |
_ENDIAN_LITTLE |
To compile for a Little Endian processor |
_ENDIAN_BIG |
To compile for a Big Endian processor |
_ENDIAN_LITTLE
and _ENDIAN_BIG
are used to
discriminate between Little Endian and Big Endian processors. Currently,
_ENDIAN_LITTLE
is enabled only for Intel or AMD processors (32- and
64-bit versions). Use these flags instead of _WINDOWS_SOURCE
which
is dedicated to Windows platforms and not available for other platforms.
Check the compilation errors and warnings against the Most Common Errors and Solutions. You can also use the portable V5 Generic Basic Types.
You can check your code against the Most Common Errors and Solutions
Once the application builds, run your ODTs to find out run time problems, and correct them.
As a default behavior, mkmk commands, such as mkmk, mkodt, mkrun, and mkCheckSource allow 64-bit code to run on 64-bit platforms. To use these tools to build, test, run, and check 32-bit code on a 64-bit platform, set the _MkmkOS_BitMode variable to 32 before running the tck_init profile:
set _MkmkOS_BitMode=32 t:tck_init.bat // assuming the RADE tool installation is mapped to the t: drive
For example, to build your frameworks in 32-bit mode on a 64-bit machine, type:
set _MkmkOS_BitMode=32 t:tck_init // assuming the ENOVIA Studio tool installation is mapped to the t: drive tck_profile MyTCK adl_ch_ws MyWorkspace mkmk -auw
To deploy you application, refer to [3].
Some generic basic types with explicit names help you to write code independent from the platform without using the preprocessor flags described above. Theses types (and macros) allow you to port your code without runtime impacts on 32-bit because you can modify your code keeping it identical when compiling on 32-bit platforms.
For example:
These generic basic types are available in the CATDataType.h file of the SpecialAPI framework:
Types | UNIX | Windows | ||||||
---|---|---|---|---|---|---|---|---|
32-bit | Length | 64-bit | Length | 32-bit | Length | 64-bit | Length | |
CATINT32 | int | 4 | int | 4 | int | 4 | int | 4 |
CATUINT32 | unsigned int | 4 | unsigned int | 4 | unsigned int | 4 | unsigned int | 4 |
CATLONG32 | long | 4 | int | 4 | long | 4 | long | 4 |
CATULONG32 | unsigned long | 4 | unsigned int | 4 | unsigned long | 4 | unsigned long | 4 |
CATLONG64 | long long | 8 | long long | 8 | _int64 | 8 | _int64 | 8 |
CATULONG64 | unsigned long long | 8 | unsigned long long | 8 | unsigned _int64 | 8 | unsigned _int64 | 8 |
LONG | long | 4 | int | 4 | long | 4 | long | 4 |
ULONG | unsigned long | 4 | unsigned int | 4 | unsigned long | 4 | unsigned long | 4 |
CATINTPTR | int | 4 | intptr_t | 8 | __w64 int | 4 | INT_PTR | 8 |
CATUINTPTR | unsigned int | 4 | uintptr_t | 8 | __w64 unsigned int | 4 | UINT_PTR | 8 |
CATLONGPTR | long | 4 | intptr_t | 8 | __w64 long | 4 | LONG_PTR | 8 |
CATULONGPTR | unsigned long | 4 | uintptr_t | 8 | __w64 unsigned long | 4 | ULONG_PTR | 8 |
CATLONGINT | long | 4 | long | 8 | long | 4 | long | 4 |
CATULONGINT | unsigned long | 4 | unsigned long | 8 | unsigned long | 4 | unsigned long | 4 |
Note: Lengths are expressed in bytes.
Avoid using long because its size differs from Windows to UNIX in 64-bit. Use LONG or ULONG instead.
In addition, some macros help to convert pointers to different types, and the reverse:
Macro | Equivalent to | Length | |
---|---|---|---|
32-bit | 64-bit | ||
CATPtrToINT32(p) | ((CATINT32)(CATLONGPTR)(p)) | 4 | 4 |
CATPtrToUINT32(p) | ((CATUINT32)(CATULONGPTR)(p)) | 4 | 4 |
CATPtrToLONG32(p) | ((CATLONG32)(CATLONGPTR)(p)) | 4 | 4 |
CATPtrToULONG32(p) | ((CATULONG32)(CATULONGPTR)(p)) | 4 | 4 |
CATINT32ToPtr(p) | ((void*)(CATLONGPTR)(p)) | 4 | 8 |
CATUINT32ToPtr(p) | ((void*)(CATULONGPTR)(p)) | 4 | 8 |
CATLONG32ToPtr(p) | ((void*)(CATLONGPTR)(p)) | 4 | 8 |
CATULONG32ToPtr(p) | ((void*)(CATULONGPTR)(p)) | 4 | 8 |
When you modify a virtual method signature, for example moving an int to a CATINTPTR, you have to propagate the same modification on all the re-definitions of this method (of course in base classes too). This is mandatory for 64-bit runtime. If you don't do this you break polymorphism mechanisms on 64-bit runtime. Concerning 32-bit, even if you don't migrate these re-definitions the runtime works because in fact you didn't change the signature.
The most common errors that you will have to deal with may be due to:
This deals with the scope of variables declared in for statement.
Note: This is not dedicated to 64-bit, but happen with the compiler used. The same behavior will occur in 32-bit also when using VC 7.1.
The new ANSI C++ standard specifies that variables declared as in for
(int i=0; ...)
have a scope local to the for statement. Nevertheless,
older compilers like Visual C++ 5.0 use the older concept that the scope is the
enclosing group. Below, are two possible problems arising from this change and
their recommended solutions.
for( int i=0; i<N; i++ ) ... if ( i == N ) ...
Since i is used outside of the for scope, the compilation fails with compilers compliant with the new ANSI C++. If you want to use the variable after the for statement, declare it before the for statement:
int i; for ( i=0; i<N; i++ ) ... if (i==N) ...
The compilation fails also when using the same variable in multiple for loops, such as:
for( int i=0; i<N; i++ ) ... for( i=0; i<M; i++ ) ...
You can declare the variable outside of the for statement:
int i; for( i=0; i<N; i++ ) ... for( i=0; i<M; i++ ) ...
You can also use a variable specific to each for:
for( int i=0; i<N; i++ ) ... for( int j=0; j<M; j++ ) ...
Data alignment deals with data location in memory. 64-bit compilers align data, that is, locate data at an address that is a multiple of its length, provided this data is less or equal to 8 bytes in length. Alignment defaults may occur in buffers and can throw exceptions and/or crash the application much more often than in 32-bit.
For more information, refer to the MSDN article about data alignment [4].
Example: Computing 32-bit hash key from a pointer resulting from an allocation:
unsigned int CATMyHashIt( void * iObj ) { unsigned int key=0; if ( iObj ) { #ifdef PLATEFORME_DS64 key = CATPtrToUINT32(iObj) >> 3; // Generally in 64-bit allocations are aligned on a boundary of 8 bytes #else key = CATPtrToUINT32(iObj) >> 2; // Generally in 32-bit allocations are aligned on a boundary of 4 bytes #endif } return key; }
In 64-bit, the key is the pointer cast to an unsigned int the length of which is 4 bytes. This unsigned int is then processed by the right bitwise shift operator that shifts it right 3 positions, that is, divides it by 8. In 32-bit mode, it is divided by 4.
When casting a pointer to a 32-bit basic type like an int, you have to know if it is a real 64-bit address or not. If not you can use CATPtrTo prefixed macros to make your code compile without warning. Otherwise you have to use the 64-bit equivalent type, for example CATINTPTR instead of an int thus you do not modify the 32-bit view of your code.
Example 1: Typical compilation error or warning occurring when setting a send/receive callback that passes an integer to the calback method:
int info = ... ; _selCallBack = AddAnalyseNotificationCB (label, label->GetActivateNotification(), (CATCommandMethod)&MyStateCommand::MyCallback, (CATCommandClientData) info); ... void MyStateCommand::MyCallback(CATCommand* iFromClient, CATNotification* iNotif, CATCommandClientData iData) { int ipos = (int) iData; ... }
The integer value is passed to the callback method MyCallback
thanks to the
CATCommandClientData
, that is, a pointer to void. As usual in 32-bit, the
int and the pointer have both the same size of 4 bytes. In 64-bit mode, the
pointer is 8-byte long, but the int remains 4-byte long.
Even if there is no loss of data when putting 4 bytes in 8, and then retrieving
the first 4 bytes from the _ bytes of the pointer, compilers will issue errors
or warnings. To fix them, write:
int info = ... ; _selCallBack = AddAnalyseNotificationCB (label, label->GetActivateNotification(), (CATCommandMethod)&MyStateCommand::MyCallback, (CATCommandClientData) CATINT32ToPtr(info)); ... void MyStateCommand::MyCallback(CATCommand* iFromClient, CATNotification* iNotif, CATCommandClientData iData) { int ipos = CATPtrToINT32(iData); ... }
First cast the int as a 32-bit pointer using CATINT32ToPtr in the AddAnalyseNotificationCB method that sets the callback, and then cast the pointer back to an int thanks to CATPrtToINT32 in the callback method.
Example 2: When casting a pointer to pointer into an (unsigned
int *)
for example, compilers do not issue any warning or error to help
migration in case of non 64-bit portable instruction.
MyClass ** ppClass = ...; unsigned int * uiClass = (unsigned int *) ppClass; for ( int i=0; i<size ; i++ ) { MyClass * pClass = ppClass[i]; uiClass[i] = pClass->Method(); // destination zone is invalid and 32-bit are set instead of 64-bit. }
To fix this problem:
MyClass ** ppClass = ...; CATUINTPTR * uiClass = (CATUINTPTR *) ppClass; for ( int i=0; i<size ; i++ ) { MyClass * pClass = ppClass[i]; uiClass[i] = pClass->Method(); }
unsigned int count = 0; ... iValue.ConvertToUTF16( utf16String, (size_t *) & count );
To fix this problem:
size_t count = 0; // You can also use an intermediate temporary variable of size_t type. ... iValue.ConvertToUTF16( utf16String, & count );
To avoid this kind of problems, check all the casts by reference or address for the following types:
Most of time, use an intermediate local variable of the right type!
Note: If your application runs specifically on Windows, casting an int as (long&), for example, is not a problem because these types are both 32-bit length. In this case you are interested in casting size_t to int or long.
Avoid unions as far as possible. Do not use types in unions whose length is different from an operating system to another.
Unions are sometimes used to manipulate pointers as integers. For example:
union { void * ptr; unsigned int ptrAsInt; }
This compiles in 32-bit because the pointer and the unsigned int sizes are both 4 bytes. This does not compile with 64-bit, because the pointer size passes to 8 bytes while the unsigned int size remains 4 bytes. To fix this problem, use CATUINTPTR that stores a pointer using the appropriate types depending on the operating system is Windows or UNIX in either 32-bit or 64-bit mode:
union { void * ptr; CATUINTPTR ptrAsInt; }
Another union example is an equivalence between a simple type, say an unsigned long, and a table of chars:
union { unsigned long lwEq; char bytes [4]; }
This compiles in 32-bit because the unsigned long and a table of chars sizes are both 4 bytes. This does not compile with UNIX 64-bit, because the unsigned long size passes to 8 bytes while the table of chars size remains 4 bytes. To fix this problem, use CATULONG32 that stores an unsigned long using its appropriate size depending on the operating system is Windows or UNIX in either 32-bit or 64-bit mode:
#include “CATDataType.h†... union { CATULONG32 lwEq; char bytes [4]; };
Pay attention to results you may not expect when multiplying an int by an unsigned int, because the result, as specified by the C ANSI, is unsigned int!
For example:
unsigned int size = ...; int i = -1; void * tab; tab [ icurrent + size * i ] access to unexpected memory area.
Multiplying size by i (-1) produces an unsigned int. The result of
icurrent + size * i
is greater than icurrent
, while the
result is expected as smaller. An unexpected memory area might then be
accessed.
To print out a pointer or an integer value, use the appropriate format specifiers.
In case of printing a pointer value, do not use %x or %X, but use %p instead.
The following code will print a truncated pointer value:
printf("%x", ptr_value) // %x is a 32-bit field and will truncate the pointer
To fix this problem:
printf("%p",ptr_value) // change to %p to display the full pointer value
With UNIX only, to display or read an unsigned int or an int, do not use %u or %d with printf or scanf, but use %lu or %ld instead.
In the following example, the argument is incompatible with the corresponding format string conversion:
CATUINTPTR M = ... CATINTPTR N = ... fprintf( stdout, "size=%u number=%d \n", M, N );
To fix this problem:
CATUINTPTR M = ... CATINTPTR N = ... fprintf( stdout, "size=%lu number=%ld \n", M, N );
long a = 0xffff; // Assumes a long is 4 bytes void *ptr = 0xffff; // Assumes a pointer is 4 bytes
To fix this problems, for instance you can extend these values:
#ifdef PLATEFORME_DS64 long a = 0xffffffff; void *ptr = 0xffffffff; #else long a = 0xffff; void *ptr = 0xffff; #endif
When you do not define the signature of a function in a C file, the compiler assumes the function returns an int.
This problem is not specific to 64-bit. Forgetting including math.h is a classical problem (in 32-bit as well as in 64-bit) because then using functions returning a double will be considered as returning an int and the result is truncated before being copied to the destination double variable.
With 64-bit, this problem also applies to functions returning pointers (or long integers), like malloc which is defined in stdlib.h, or strdup which is defined in string.h.
In addition, this problem does not only concern standard C libraries but also your own libraries, so you have to include or explicitly define the right signature of all used functions, particularly those that do not return an int.
[1] | 64-bit Windows |
[2] | General Porting Guidelines |
[3] | CAA Partner Product Installation |
[4] | Data Alignment with 64-bit Windows |
[Top] |
This article helps you prepare your applications to support 64-bit operating systems. The proposed methodology consists in setting the appropriate 64-bit environment, compiling your application to first analyze and correct the build time errors or warnings. When the application builds, then look for and correct run time errors shown when replaying your ODTs.
This article explains the differences between 32-bit and 64-bit operating systems, explains the most common errors found when preparing applications for 64-bit along with the appropriate solutions.
Version: 2 [Apr 2008] | Document updated for 32-bit code built on a 64-bit machine with Windows |
Version: 1 [May 2004] | Document created |
[Top] |
Copyright © 1994-2008, Dassault Systèmes. All rights reserved.