Lessons on Development of 64-Bit C/C++ Applications

Introduction

Developers working with other 64-bit operating systems will learn much interesting as well. The course will consider all the steps of creating a new safe 64-bit application or migrating the existing 32-bit code to a 64-bit system.

The course is composed of 28 lessons devoted to introduction to 64-bit systems, issues of building 64-bit applications, methods of searching errors specific to 64-bit code and code optimization. Such questions are also considered as estimate of the cost of moving to 64-bit systems and rationality of this move.

The course is devoted to creation of 64-bit applications in C/C++ language and is intended for the Windows developers who use Visual Studio 2005/2008/2010 environment. Developers working with other 64-bit operating systems will learn much interesting as well. The course will consider all the steps of creating a new safe 64-bit application or migrating the existing 32-bit code to a 64-bit system.

The course is composed of 28 lessons devoted to introduction to 64-bit systems, issues of building 64-bit applications, methods of searching errors specific to 64-bit code and code optimization. Such questions are also considered as estimate of the cost of moving to 64-bit systems and rationality of this move.

The authors of the course: candidate of physico-mathematical sciences Andrey Nikolaevich Karpov and candidate of technical sciences Evgeniy Alexandrovich Ryzhkov. The authors are involved in maintaining the quality of 64-bit applications and participate in development of PVS-Studio static code analyzer for verifying the code of resource-intensive applications.

The rightholder of the course is OOO “Program Verification Systems”. The company’s site: http://www.viva64.com. Contacts: e-mail: [email protected], 300027, Tula, PO box 1800.

The contents of the course

  • Lesson 01. What 64-bit systems are.
  • Lesson 02. Support of 32-bit applications.
  • Lesson 03. Porting code to 64-bit systems. The pros and cons.
  • Lesson 04. Creating the 64-bit configuration.
  • Lesson 05. Building a 64-bit application.
  • Lesson 06. Errors in 64-bit code.
  • Lesson 07. The issues of detecting 64-bit errors.
  • Lesson 08. Static analysis for detecting 64-bit errors.
  • Lesson 09. Pattern 01. Magic numbers.
  • Lesson 10. Pattern 02. Functions with variable number of arguments.
  • Lesson 11. Pattern 03. Shift operations.
  • Lesson 12. Pattern 04. Virtual functions.
  • Lesson 13. Pattern 05. Address arithmetic.
  • Lesson 14. Pattern 06. Changing an array’s type.
  • Lesson 15. Pattern 07. Pointer packing.
  • Lesson 16. Pattern 08. Memsize-types in unions.
  • Lesson 17. Pattern 09. Mixed arithmetic.
  • Lesson 18. Pattern 10. Storage of integer values in double.
  • Lesson 19. Pattern 11. Serialization and data interchange.
  • Lesson 20. Pattern 12. Exceptions.
  • Lesson 21. Pattern 13. Data alignment.
  • Lesson 22. Pattern 14. Overloaded functions.
  • Lesson 23. Pattern 15. Growth of structures’ sizes.
  • Lesson 24. Phantom errors.
  • Lesson 25. Working with patterns of 64-bit errors in practice.
  • Lesson 26. Optimization of 64-bit programs.
  • Lesson 27. Peculiarities of creating installers for a 64-bit environment.
  • Lesson 28. Estimating the cost of 64-bit migration of C/C++ applications.

The course’s duration: the course implies that you study each of the 28 lessons on your own in 20-40 minutes. The total time of studying the material is about 18 hours.

Lesson 1. What 64-bit systems are

By the moment of writing the course, there are two most popular 64-bit architectures of microprocessors: IA64 and Intel 64.

  1. IA-64 is a 64-bit microprocessor architecture developed by Intel and Hewlett Packard companies together. It is implemented in Itanium and Itanium 2 microprocessors. To learn more about the architecture IA-64 see the following Wikipedia article “Itanium“.
  2. Intel 64 (EM64T / AMD64 / x86-64 / x64) is an extension of x86 architecture with full backward compatibility. There are many variants of its name and it causes some confusion, but all these names mean the same thing: x86-64, AA-64, Hammer Architecture, AMD64, Yamhill Technology, EM64T, IA-32e, Intel 64, x64. To learn how so many names appeared see the article in Wikipedia: “X86-64“.

You should understand that IA-64 and Intel 64 are absolutely different, incompatible with each other, microprocessor architectures. Within the scope of this course we will consider only Intel 64 (x64 / AMD64) architecture as the most popular among applied Windows software developers. Accordingly, when we mention Windows operating system, we will mean its 64-bit versions for Intel 64 architecture. For example: Windows XP Professional x64 Edition, Windows Vista x64, Windows 7 x64. The program model Intel 64 available to a programmer in a 64-bit Windows is called Win64, for short.

Intel 64 architecture

The information given here is based on the first volume of the documentation “AMD64 Architecture Programmer’s Manual. Volume 1. Application Programming“.

The architecture Intel 64 we are considering here, is a simple yet powerful extension of the obsolete commercial architecture x86 with backward compatibility. It adds the 64-bit address space and extends resources to support higher performance of recompiled 64-bit programs. The architecture supports obsolete 16-bit and 32-bit code of applications and operating systems without modifying or recompiling them.

The need of a 64-bit architecture is determined by the applications that require a larger address space. First of all, these are high-performance servers, data managers, CAD and, of course, games. These applications will get great benefits from the 64-bit address space and larger number of registers. Few registers available in the obsolete x86 architecture limit performance of computing tasks. The increased number of registers provides the necessary performance for many applications.

Let us point out the main advantages of the architecture x86-64:

  • the 64-bit address space;
  • an extended register set;
  • a command set familiar to developers;
  • the capability to launch obsolete 32-bit applications in a 64-bit operating system;
  • the capability to use 32-bit operating systems.

64-bit operating systems

Nearly all modern operating systems have versions for Intel 64 architecture. For example, Microsoft ships Windows XP x64. Large UNIX developers also ship 64-bit versions, for example, Linux Debian 3.5 x86-64. But it does not mean that the whole code of such a system is 64-bit. Some parts of the operating system and many applications may well remain 32-bit because Intel 64 provides backward compatibility. Thus, the 64-bit version of Windows uses a special mode WoW64 (Windows-on-Windows 64) that translates the calls of 32-bit applications to the resources of the 64-bit operating system.

Address space

Although a 64-bit processor can theoretically address 16 Ebytes of memory (2^64), Win64 now supports only 16 Tbytes (2^44). There are some reasons for that. Contemporary processors can provide access only to one Tbyte (2^40) of physical memory. The architecture (but not the hardware part) can extend this space up to 4 Pbytes (2^52). But in this case you need an immense amount of memory for the page tables representing it.

Besides the limitations described above, the size of memory available in every particular version of the 64-bit Windows depends upon the commercial reasons of Microsoft. Different Windows versions have different limitations which are illustrated in the table.


Table 1 – The amounts of memory supported in different Windows versions

Win64 program model

Like in Win32, the size of a page in Win64 is 4 Kbytes. The first 64 Kbytes of the address space are never displayed, so the lowest correct address is 0x10000. Unlike Win32, system DLL’s take more than 4 Gbytes.

Compilers for Intel 64 have one peculiarity: they can use registers with great efficiency to pass parameters into functions instead of using the stack. This allowed the Win64 architecture developers to get rid of such a notion as a calling convention. In Win32, you may use various conventions: __stdcall, __cdecl, __fastcall, etc. In Win64, there is only one calling convention. Here is an example of how four arguments of integer type are passed through registers:

  • RCX: the first argument
  • RDX: the second argument
  • R8: the third argument
  • R9: the fourth argument

The arguments following the first four integer ones are passed through the stack. To pass float arguments XMM0-XMM3 registers are used as well as the stack.

The difference in the calling conventions makes it impossible to use both 64-bit and 32-bit code in one program. In other words, if an application has been compiled for the 64-bit mode, all the libraries (DLL) being used must also be 64-bit.

Passing parameters through registers is one of the innovations that make 64-bit programs faster than 32-bit ones. You may achieve an additional performance gain using 64-bit data types. We will tell you about it in the next lesson.

Lesson 2. Support of 32-bit applications in the 64-bit Windows environment

Before we start discussing the topic of developing 64-bit program code, let us speak about backward compatibility of 64-bit Windows versions with 32-bit applications. Backward compatibility is arranged through the mechanisms implemented in WoW64.

WoW64 (Windows-on-Windows 64-bit) is a subsystem of Windows operating system that allows you to execute 32-bit applications on all the 64-bit versions of Windows.

The WoW64 subsystem does not support the following programs:

  • programs compiled for 16-bit operating systems;
  • kernel-mode programs compiled for 32-bit operating systems.

Indirect expenses

Different processor architectures have a bit different WoW64. For example, the 64-bit Windows version developed for Intel Itanium 2 processor employs WoW64 to emulate x86 instructions. This emulation is rather resource-intensive in comparison to WoW64 for Intel 64 architecture because the system has to switch from the 64-bit mode to compatibility mode when executing 32-bit programs.

WoW64 on Intel 64 (AMD64 / x64) does not require instruction emulation. In this case the WoW64 subsystem emulates only the 32-bit environment through an additional layer between a 32-bit application and the 64-bit Windows API. In some places this layer is thin, in others a bit thicker. For an average program, you may expect 2% performance penalty because of this layer. For some programs, it can be larger. Two per cent is not very much but keep in mind that 32-bit applications work a bit slower under the 64-bit Windows than in the 32-bit environment.

Compilation of 64-bit code does not only allow you to avoid using WoW64 but also gives you an additional performance gain. This is explained by architectural modifications in the microprocessor such as an increased number of general-purpose registers. For an average program, you may expect a 5-15% performance gain after mere recompilation.

Benefits of the 64-bit environment for 32-bit programs

Because of the WoW64 layer, 32-bit programs are less efficient in the 64-bit environment than in their native 32-bit one. But still simple 32-bit applications can get one benefit of being executed in the 64-bit environment. Maybe you know that a program built with the switch “/LARGEADDRESSAWARE:YES” can allocate up to 3 Gbytes of memory if a 32-bit Windows is launched with the switch “/3gb”. Well, the same 32-bit program built on a 64-bit system can allocate almost 4 Gbytes of memory (in practice it is usually about 3.5 Gbytes).

Redirections

The WoW64 subsystem isolates 32-bit programs from 64-bit ones by redirecting calls to files and the register. It helps to keep 32-bit programs from accidentally accessing the data of 64-bit ones. For example, a 32-bit application that launches a DLL file from the catalogue “%systemroot%\System32” can accidentally address a 64-bit DLL which is incompatible with the 32-bit program. To avoid this, the WoW64 subsystem redirects the access from the folder “%systemroot%\System32” into the folder “%systemroot%\SysWOW64”. This redirection helps you avoid compatibility errors because the 32-bit application will need a special DLL file created to work with 32-bit applications.

To learn more about the mechanisms of file system and register redirection see MSDN section “Running 32-bit Applications“.

Why cannot 32-bit DLL’s be used in a 64-bit program? Is there a way to evade this limitation?

It is impossible to load a 32-bit DLL from a 64-bit process and execute its code. It is impossible due to the design of 64-bit systems. It is impossible fundamentally. And no tricks and undocumented means will help you. To do this you will have to load and initialize WoW64, not to speak of the kernel structures. Actually, it means that a 64-bit process must be made 32-bit “on the fly”. This topic is described more thoroughly in the post “Why can’t you thunk between 32-bit and 64-bit Windows?”. The only thing we can recommend is to create a surrogate process and work with it through the COM technology. You may read about it in the article “Accessing 32-bit DLLs from 64-bit code“.

But it is quite easy to load resources from a 32-bit DLL into a 64-bit process. You may do it specifying the flag LOAD_LIBRARY_AS_DATAFILE when calling LoadLibraryEx.

Gradual renunciation of 32-bit software support

It will be quite natural if Microsoft company will stimulate the move to 64-bit systems by gradually canceling the support of 32-bit programs in some versions of Windows operating system. Of course it will be a very slow process but the first steps in this direction have been already made.

Many administrators know about a relatively new installation and operation mode of the server version of the operating system called Server Core. It is that very mode the participants of “Windows vs Linux” wars have been speaking of for a long time. One of the reasons that adherents of using Linux on servers referred to was the capability to install the server operating system without graphical interface (GUI). But here is such a capability in Windows Server too. Now, if you install the system in this mode, you will get only the command line without user interface.

This capability (Server Core installation) appeared in Windows Server 2008. Yet in Windows Server 2008 R2 there is another innovation that brings closer the 64-bit future. Support of 32-bit applications is now optional and you may enable or disable it when installing Windows Server 2008 R2 (Server Core). Moreover, this option is disabled by default. So when trying to launch a 32-bit application in Server Core mode, you will get a message telling you that it is impossible. Of course you may add 32-bit application support:

start /w ocsetup ServerCore-WOW64

In the usual (Full Installation) mode, execution of 32-bit applications is enabled by default, but not in Server Core.

The tendency is obvious. It will be more and more rational to create 64-bit versions of applications in time as they will be able to work on more operating system versions.

Additional information

Alexey Pahunov’s Russian blog is also a very interesting source of information on WoW64: http://blog.not-a-kernel-guy.com/. Alexey is Microsoft company’s worker and he personally participates in developing the WoW64 subsystem.

Lesson 3. Porting code to 64-bit systems. The pros and cons

You should begin studying 64-bit systems with the question “How much rational it will be to recompile a project for a 64-bit system?”. You must answer this question but take your time and think it over. On the one hand, you might lag behind your rivals failing to offer 64-bit solutions on market. On the other hand, you might waste your time developing a 64-bit application that will have no competitive advantages.

Here are some factors that will help you make a choice.

Application life-cycle

You should not create a 64-bit version of an application with a short life-cycle. The WoW64 subsystem allows obsolete 32-bit applications to work rather well on 64-bit Windows systems. It is unreasonable to make a program 64-bit if you stop maintaining it in 2 years. The practice shows that the move to 64-bit Windows versions will be very slow and smooth. Perhaps most of your users will use only the 32-bit version of your program solution in the nearest future. You should keep in mind that this course was written in 2009 when most users were working with 32-bit versions of operating systems. But in time 32-bit programs will look more and more unnatural and outdated.

If you plan a prolonged development and maintenance of your program product, you should start working on its 64-bit version. Of course you should take your time but keep in mind that the later you have a full 64-bit version, the more problems you are to encounter while maintaining such an application installed on 64-bit Windows versions.

Application performance requirements

After being recompiled for a 64-bit system a program can use huge amounts of memory and its speed will increase in 5-15%. 5-10% of speed gain is achieved due to architectural features of the 64-bit processor, for example, a larger number of registers. And another 1-5% performance gain is determined by the absence of the WoW64 layer that translates calls between 32-bit applications and the 64-bit operating system.

For example, Adobe company says that a new 64-bit “Photoshop CS4″ is 12% faster than its 32-bit version”.

Applications involving large memory amounts can expect a great performance gain. These are graphical editors, CAD-systems, GSI CAD, databases and packages for modeling various processes. The capability to store all the data in memory and therefore avoid additionally loading them from the hard disk may increase the speed of such applications not in some per cent but in several times.

For example, take Alfa-Bank that integrated an Itanium 2 based platform into their IT-infrastructure. The growth of their investment business had caused the system to fail to manage the increasing load on the current configuration any more: the number of customer support delays sometimes got very critical. The analysis of the situation showed that the bottleneck of the system had nothing to do with processors’ performance but it was the limitation of the 32-bit architecture regarding the memory subsystem that allowed using not more than 4 Gbytes of the server address space. The database size was more than 9 Gbytes. It had been used very intensively and that caused a critical loading of the input-output subsystem. Alfa-Bank decided to buy a cluster of two four-processor servers based on Itanium 2 with 12 Gbytes of memory. This decision allowed them to get the necessary performance and fault-tolerance level. As the company representatives say, introduction of Itanium 2 based servers allowed them to eliminate serious issues and manage to save much money.

Using third-party libraries in a project

Before planning the work on developing the 64-bit version of your product, make it out if there are 64-bit versions of libraries and components it employs. You should also find out the pricing policy regarding the 64-bit versions of the libraries. All this you may learn on the site of library developers. If there is no support for the libraries, search for alternative means supporting 64-bit systems beforehand.

Dependence of third-party developers upon your libraries

If you are developing libraries, components or other items intended for third-party developers to create software with, you must be quick in creating the 64-bit version of your product. Otherwise, your customers interested in 64-bit versions will have to search for other solutions. For example, some soft- and hardware security developers appeared to be very late in creating 64-bit programs and it made some of their clients choose other tools to protect their software products.

There is one more benefit of releasing a 64-bit version of a library: you may sell it as a separate product. Thus, your customers who wish to create both 32-bit and 64-bit applications will have to buy 2 different licenses. For example, Spatial Corporation company sticks to such a policy when selling their library Spatial ACIS.

16-bit applications

If your solutions still have 16-bit modules, you must get rid of them. 64-bit Windows versions do not support 16-bit applications.

I should explain one thing here related to using 16-bit installers. They are still used to install some 32-bit applications. There exists a special mechanism that replaces some of the most popular 16-bit installers with their more contemporary versions on the fly. It might make you think that 16-bit programs still work in the 64-bit environment, but it is a mistake, please, keep it in mind.

Assembler code

Do not forget that presence of large assembler code fragments make it much more expensive to create the 64-bit version of an application.

Toolkit

If you have decided to create the 64-bit version of your product relying on the factors mentioned above and are ready to spend time on it, the success is not guaranteed yet. You should have all the necessary tools for that and here you might encounter some very unpleasant things.

The most obvious yet most serious problem is absence of a 64-bit compiler. When we were writing this text (2009) there was no 64-bit C++ Builder compiler by Embarcadero yet. Its release was expected by the end of 2009. You cannot evade this problem unless you rewrite the whole project employing, for example, Microsoft Visual Studio. But while everything is clear in case of compiler absence, other similar issues might be not so obvious and occur only at the step of porting the project to a new architecture. You should make a research beforehand to find out if you can get all the necessary components to implement the 64-bit version of your product. You might face unpleasant surprises.

While making a decision, please keep in mind the last very important factor we have not mentioned here: the price of modifying your program code to compile it in the 64-bit mode. We will tell you how to estimate this price in one of the following lessons. It may be very high and must be considered in planning and scheduling.

Lesson 4. Creating the 64-bit configuration

Compiler

At first you should make sure that the Visual Studio edition you are using allows building 64-bit code. If you want to develop 64-bit applications using the latest (at the moment of writing this course) Visual Studio 2008 version, here is a table that will help you understand what Visual Studio edition you need.


Table 1 – Capabilities of different Visual Studio 2008 editions

If the Visual Studio edition you are using allows creating 64-bit code, you should check if the 64-bit compiler is installed. Figure 1 shows the page of installing Visual Studio 2008 components where installation of the 64-bit compiler is disabled.


Figure 1 – The 64-bit compiler is disabled when installing Visual Studio 2008

Creating the 64-bit configuration

Creating the 64-bit version of a project in Visual Studio 2005/2008 is a rather simple procedure. Difficulties will appear later at the step of building the new configuration and searching for errors in it. To create a 64-bit configuration you should make the following 4 steps:

Step 1

Open the configuration manager as shown in Figure 2:


Figure 2 – Launching the configuration manager

Step 2

Choose support of the new platform in the configuration manager (Figure 3):


Figure 3 – Creating a new configuration

Step 3

Choose the 64-bit platform (x64) and take the 32-bit version settings as the base (Figure 4). Visual Studio environment will automatically modify those settings that impact the build mode.


Figure 4 – Choosing x64 as the platform and loading the Win32 configuration as the base

Step 4

You have added the new configuration and now may select the 64-bit configuration version and start compiling the 64-bit application. Figure 5 shows how to choose the 64-bit building configuration.


Figure 5 – Now you have both the 32-bit and 64-bit configurations

Modifying parameters

If you are lucky, you will not have to adjust the 64-bit project. But this depends upon the project, its complexity and the number of libraries being used. The only thing you should modify right away is the stack size. If your project uses the stack of the default size, i.e. 1 Mbyte, you should change it to 2 Mbytes for the 64-bit version. It is not necessary but it is better to secure yourself from possible issues beforehand. If you use the stack of a size different from that by default, you should make it twice larger for the 64-bit version. To do it find and change the parameters Stack Reserve Size and Stack Commit Size in the project settings (see Figure 6).


Figure 6 – Location of project settings defining the stack size

What next?

Having the 64-bit configuration of a project does not mean that it will compile well and work at all. The process of compilation and detection of hidden errors will be discussed in the next lessons.

Lesson 5. Building a 64-bit application

We would like to warn the readers right away that it is impossible to describe the process of building a 64-bit application in every detail. Any project has its own unique settings, so you must be very attentive when adapting them for a 64-bit system. The lesson discusses only the common steps important for any project. These steps will tell you where to begin.

Libraries

Before trying to build your 64-bit application, make sure that all the necessary versions of 64-bit libraries are installed and paths to them are correct. For example, 32-bit and 64-bit library files with “lib” extension usually differ and are situated in different catalogues. Fix the bugs if any.

Note. If libraries are presented in the form of the source code, there must be the 64-bit configuration of the project. Keep in mind that you are risking to infringe license agreements when modifying a library to build its 64-bit version by yourself.

Assembler

Visual C++ does not support the 64-bit inline assembler. You must either use an external 64-bit assembler (for example, MASM) or rewrite the assembler code in C/C++.

Examples of compilation errors and warnings

On starting to build the project you will encounter many compilation errors and warnings related to explicit and implicit type conversions. We would like to show you an example of such an error. Here is a code:

void foo(unsigned char) {}
void foo(unsigned int) {}

void a(const char *str)
{
  foo(strlen(str));
}

This code successfully compiles in the 32-bit mode, but in the 64-bit mode, Visual C++ compiler will generate the warning:

error C2668: 'foo' : ambiguous call to overloaded function
   .\xxxx.cpp(16): could be 'void foo(unsigned int)'
   .\xxxx.cpp(15): or 'void foo(unsigned char)'
   while trying to match the argument list '(size_t)'

The function strlen() returns the type size_t. On a 32-bit system, the type size_t coincides with the type unsigned int and the compiler chooses the function “void foo(unsigned int)” to call. In the 64-bit mode, the types size_t and unsigned int do not coincide. The type size_t becomes 64-bit while the type unsigned int remains 32-bit. As a result, the compiler does not know which of the foo() functions to prefer.

Now consider an example of a warning generated by Visual C++ compiler when building code in the 64-bit mode:

CArray<char, char> v;
int len = v.GetSize();

warning C4244: 'initializing' : conversion from 'INT_PTR' to 'int',
possible loss of data

The function GetSize() returns the type INT_PTR that coincides with the type int in a 32-bit code. In a 64-bit code, the type INT_PTR is 64-bit and it is implicitly converted to the 32-bit int type. The values of more significant bits get lost during this process and the compiler warns you about it. An implicit type conversion may cause an error if the number of the array items exceeds INT_MAX. To eliminate the warning and the possible error you should assign the type INT_PTR or ptrdiff_t to “len” variable.

Do not correct warnings until you have learned the 64-bit error patterns. You might accidentally hide an error failing to correct it and make it more difficult to detect further. You will learn about the patterns of 64-bit errors and methods of detecting and correcting them in the next lessons. You may also see the following articles: “20 issues of porting C++ code on the 64-bit platform“, “A 64-bit horse that can count“.

size_t and ptrdiff_t types

As most compilation errors and warnings are related to data type incompatibility, we should consider two types – size_t and ptrdiff_t – which are most relevant to us regarding the process of 64-bit code creation. If you are using Visual C++ compiler, these types are integrated into it and you will not need the library files. But if you are using GCC, you will need the header file “stddef.h”.

size_t is a C/C++ base unsigned integer type. It is the type of the result returned by sizeof operator. The size of the type is chosen so that it could store the maximum size of a theoretically possible array of any type. For example, size_t is 32-bit on a 32-bit system and 64-bit on a 64-bit one. In other words, you may safely store a pointer in a variable of size_t type. Pointers to class functions are an exception but this is a different topic. The type size_t is usually used in loop counters, to index arrays, to store sizes and in address arithmetic. The following types are analogous to size_t: SIZE_T, DWORD_PTR, WPARAM, ULONG_PTR. Although you may store a pointer in size_t, it is better to use another unsigned integer type uintptr_t for that – its name reflects its capability. The types size_t and uintptr_t are synonyms.

ptrdiff_t is a C/C++ base signed integer type. Its size is chosen so that it could store the maximum size of a theoretically possible array of any type. This type will be 32-bit on a 32-bit system and 64-bit on a 64-bit one. Like size_t, a variable of ptrdiff_t type can safely store a pointer except for a pointer to a class function. The type ptrdiff_t is also the result of an expression where one pointer is subtracted from another “ptr1-ptr2”. The type ptrdiff_t is usually used in loop counters, to index arrays, to store sizes and in address arithmetic. Its analogues are: SSIZE_T, LPARAM, INT_PTR, LONG_PTR. The type ptrdiff_t has a synonym intptr_t whose name reflects it more clearly that it can store a pointer.

The sizes size_t and ptrdiff_t were created to perform correct address arithmetic. It has been considered for a long time that the size of int coincides with the size of the machine word (processor capacity) and it can be used as indexes and to store sizes of objects and pointers. So, address arithmetic was also built with int and unsigned types. The type int is used in most education materials on C and C++ programming in loop bodies and as indexes. The following example is almost a canon:

for (int i = 0; i < n; i++)
  a[i] = 0;

As processors were developing and their capacity increasing, it became unreasonable to further increase the capacities of int type. There are a lot of reasons for that: the purposes of saving memory being used, maximum compatibility, etc. As a result, several data models appeared describing the relations of the base C and C++ types. So it is not so easy now to choose a type for a variable to store a pointer or object size. size_t and ptrdiff_t types appeared to become the smartest solution of this problem. They can certainly be used in address arithmetic. Now, the following code must become a canon:

for (ptrdiff_t i = 0; i < n; i++)
  a[i] = 0;

It is this code that can provide safety, good portability and performance. You will learn from the next lessons why.

The types size_t and ptrdiff_t we have described may be called memsize-types. The term “memsize” appeared as an attempt to briefly name all the types that can store sizes of pointers or indexes of the largest arrays. By memsize-types you should understand all the simple C/C++ data types that are 32-bit on a 32-bit architecture and 64-bit on a 64-bit one. Here are examples of memsize-types: size_t, ptrdiff_t, pointers, SIZE_T, LPARAM.

Lesson 6. Errors in 64-bit code

Even if you correct all compilation errors and warnings, it does not mean that a 64-bit application will work well. So it is the description and diagnosis of 64-bit errors that we will deal with in the most lessons of our course. And one more thing – do not rely on the switch /Wp64 which is described by many people (often unreasonably) in forum discussions as a wonderful tool able to find 64-bit errors.

/Wp64 switch

The switch /Wp64 allows programmers to find some issues that may occur when compiling code for 64-bit systems. The check is implemented in this way: the types marked with the key word __w64 in 32-bit code are interpreted as 64-bit types while being checked.

For example, here is a code:

typedef int MyInt32;

#ifdef _WIN64
  typedef __int64 MySSizet;
#else
  typedef int MySSizet;
#endif

void foo() {
  MyInt32 value32 = 10;
  MySSizet size = 20;
  value32 = size;
}

The expression “value32 = size;” will lead to value cutting on a 64-bit system and therefore to a possible error. We want to diagnose this issue. But when we try to compile the 32-bit application, everything is correct and there is no warning.

To get ready to move the application to 64-bit systems we need to add the switch /Wp64 and the key word __w64 when defining the type MySSizet in the 32-bit version. After that the code looks so:

typedef int MyInt32;

#ifdef _WIN64
  typedef __int64 MySSizet;
#else
  typedef int __w64 MySSizet; // Add __w64 keyword
#endif

void foo() {
  MyInt32 value32 = 10;
  MySSizet size = 20;
  value32 = size; // C4244 64-bit int assigned to 32-bit int
}

Now we get the warning C4244 that will help us in porting the code to a 64-bit platform.

Note that the switch /Wp64 is ignored in the 64-bit compilation mode because all the types already have the necessary size and the compiler performs the necessary checking. So, as you can see, we will get the warning C4244 when compiling the 64-bit version even if the switch /Wp64 is disabled.

So, the switch /Wp64 helped developers get somehow ready to use the 64-bit compiler while working with 32-bit applications. All warnings revealed with the help of /Wp64 will turn into compilation errors or remain warnings when building the 64-bit code. And that is all aid you may except from the switch /Wp64 in detecting errors.

By the way, the switch /Wp64 is considered deprecated in Visual Studio 2008 because it is high time we started to compile 64-bit applications instead of going on to get ready for it.

64-bit errors

When we speak of 64-bit errors, we mean those cases when a code fragment that works well in the 32-bit version of an application causes errors after being recompiled in the 64-bit mode. 64-bit errors occur most frequently in the following kinds of code fragments:

  • code based on wrong assumptions about type sizes (for example, an assumption that the pointer size is always 4 bytes);
  • code processing large arrays whose size is more than 2 Gbytes on 64-bit systems;
  • code responsible for data writing and reading;
  • code containing bit operations;
  • code with complex address arithmetic;
  • obsolete code;
  • and so on.

In fact, all errors occurring in the code when it is recompiled for 64-bit systems arise from inaccurate compliance with C/C++ standard ideology. But we do not find it very reasonable to follow this recommendation: “write correct programs and there will be no 64-bit errors”. One cannot argue against it but it has little relevance to real projects. There is much C/C++ code in the world that has been written for many decades. The purpose of our lessons is to arrange all the 64-bit errors into a set of patterns that will help you detect defects and instruct you how to eliminate them.

Examples of 64-bit errors

We will speak a lot about 64-bit errors in future but here are two examples for you to understand what these errors are.

The first is an example of using the magic constant 4 that serves as the size of a pointer what is incorrect in 64-bit code. Note that this code worked quite well in the 32-bit version and was not diagnosed as dangerous by the compiler.

size_t pointersCount = 100;
int **arrayOfPointers = (int **)malloc(pointersCount * 4);

The second is an example of an error in the data reading mechanism. This code is correct in the 32-bit version and the compiler does not react to it. But this code fails to correctly read the data saved by the 32-bit version of the program.

size_t PixelCount;
fread(&PixelCount, sizeof(PixelCount), 1, inFile);

A comment for sophisticated programmers

I would like to comment right away upon the 64-bit error patterns and error examples that will be discussed in many following lessons. People often argue that actually these are not errors related to 64 bits but the errors arising from an incorrectly written and badly portable code. And they also say that many errors can be found when porting code not only to the 64-bit architecture but simply to any architecture where the base types have other sizes.

Yes, that is right! We keep this in mind. But our goal is not to study the issue of code portability as such. In these lessons we are going to solve a particular local task – to help developers in mastering 64-bit platforms that become more and more popular.

When speaking of 64-bit error patterns we will consider examples of code that is correct on 32-bit systems but may cause faults when being ported to the 64-bit architecture.

More by Author

Must Read