Preparing for the Future

Over time, sources tend to be used for different purposes than those for which they were initially created. Although the original life cycle, or project scope, of sources can usually be determined quite easily, it often happens that sources are reused in different (or follow-up) projects. This means that issues can arise in different areas when sources are at some point compiled with a different compiler (compatibility) and perhaps even for a different target system (portability). This section provides tips that help you write sources that do not suffer from many of the problems encountered during reuse. Most of these tips concern standard coding practices and therefore do not take up any extra development time. This is good because in many projects you do not have the luxury to spend time on compatibility and portability issues when you have no idea whether they will ever be needed.

Compatibility

Compatibility and portability are closely related issues, but there are distinct differences between the two.

The compatibility of sources expresses how well they can be used in different development environments on the same system. Think of switching compilers, using C sources within a C++ project, compiling sources from a network drive instead of locally, and so on. The portability of sources expresses how well the sources can be used on different (target) systems. Compatibility is, in a way, a subset of portability because using sources on a different system will also entail using a different compiler. This section discusses compatibility; the next section discusses portability.

Using Vendor Specifics

In order to write sources with a high level of compatibility, it is always wise to use statements and functions as defined in the standards (ANSI) for the language you are using. However, it is not always apparent when you are using compiler (or vendor) specifics. The following example demonstrates the use of vendor-specific libraries and types.

// Compatible code.
char a[] = "Print me if you dare.";
short b = 5;

// Vendor specific code.
CString a = "Print me if you dare.";
__int16 b = 5;

Character arrays and shorts are defined in the language. However, the CString class is part of Microsoft's MFC classes and __int16 is a Microsoft-specific type so these can only be used when compiling with a Microsoft compiler. Another kind of vendor specific is the use of compiler settings and options.

#pragma pack(push, 1)
struct tightstruct { char a; int b;} ;
#pragma pack(pop)

As compiler directives are defined for a specific compiler, chances are that using a different compiler will cause some kind of syntax error on these statements. Another more tricky part of vendor specifics is the way they interpret standards. Sadly, sometimes standards specify what statements should look like and how they should behave, but not how they are implemented. Other times, standards are used before they are fully worked out. This means that different compilers will generate different executables even when using the same source as input and generating code for the same system. The following example demonstrates the expected compiler implementation.

struct Access { int a; char b;} ;

Access data;
char *p = (char*)(&data) + 4;
*p = 'B';

This code works correctly only when the fields of the structure are kept in the same order in the generated executable as they were defined in the source file. Only in that case can the memory for field b be found at an offset of 4 bytes from the start address of the structure. A compiler that moves fields around, perhaps for optimization purposes, does not guarantee that this kind of access is possible. Another such example is how comment lines are interpreted.

// start of a long comment line }
which is divided }
}
over several lines...

Each line in the preceding example is officially a comment line; however, not all compilers will recognize them as such. The last line, especially, can be problematic. The same kind of problems can occur when you try to nest comments. The following are valid comment lines:

/* a comment /* li
ne
*/

// another /* comment..*/ line

/* and yet // another nested/*
comment */

The thing to remember here is that /* */ blocks may not be nested; however, /* and // simply do not mean anything when used inside a /* */ comment block.

To make sure you are not using vendor specifics, consult documentation based on the standards or consult the compiler documentation. The compiler documentation should specify whether a certain call or type is standard or specific. Sometimes, of course, it is necessary to use vendor specifics to solve a certain problem. In that case you can keep a certain level of compatibility by investing some development time. Think of moving all specifics to separate source, or header, files. That way the compiler-dependent files can simply be replaced when changing development environment. Another option is to use the precompiler to switch between sources and parts of sources. The following example uses functions to create threads on different systems, as explained in Chapter 13, to demonstrate this:

#include <stdio.h>
#ifdef _MSC_VER
    #include <process.h>
    _beginthread(StartThreadA, 0, (void*) &startInputA);
#else
    #include <pthread.h>
    pthread_t  thread1;
    pthread_create(&thread1, NULL, StartThread, (void*) &startInputA);
#endif

Compatibility Between C and C++

As C is a syntactical subset of C++, you would perhaps not expect any difficulties when compiling "old" C sources with a C++ compiler. Sadly, there are some subtle differences that can cause compatibility problems. C++ for instance is stricter concerning default parameters. The statement extern int fn() in C will define an external function which returns an integer and has zero or more arguments. In C++ extern int fn() equals extern int fn(void) . C in turn does not understand the use of // as remark indicators. When parts of your code have to be compiled by a C compiler, because they will be part of an embedded system, for instance, it is better not to use these indicators because not all C compilers are lenient enough to allow this.

As you saw in Chapter 4 in the section on mixing C and C++, including C header files in C++ is done by placing extern "C" { } around the includes in order to have the compiler switch to C mode. When a file should include a C header and should still compile with a C++ compiler as well as a C compiler, use the following construction.

Place the following construction in the C header file:

								Cheader.h:
#ifdef __cplusplus
extern "C" {
#endif

// definitions
void CFunction(char*);

#ifdef __cplusplus
}
#endif

Then include this file, normally in either a C source or a C++ source:

								Cfile.c:
#include "Cheader.h"
...

Cppfile.cpp:
#include
								
								 "Cheader.h"
...

Portability

When you port sources to a different system, you can run into all the problems identified in the previous section Compatibility. This is because a different system will have a different development environment. However, with porting come extra problems that are system related. This section points out the areas in which systems can prove to be incompatible.

File System Characteristics

Problems with portability can be caused by something as simple as file system characteristics. For instance, some file systems are case sensitive (Process.h is a different file from process.h) while others are not (Process.h and process.h are two names for the same file). When a software project is moved from a non–case-sensitive file system to a case-sensitive file system, chances are that it will not even compile or preprocess without added development effort. Entries in the make file, resource files, and even include statements are now for the first time checked against case errors. And this is true also for the use of special characters in filenames (spaces, *, _, /, and so on) and the allowed filename lengths.

A similar kind of problem can occur when moving a project from a file system that uses symbolic links (UNIX) to one that does not. Symbolic links can be used to refer to a file from several different directories. This means the file is only physically present in one directory. However, via links placed in other directories, it seems to be located there also. When such a directory structure is copied to a file system that does not support these kinds of links, a file that is linked to it is simply copied to each place where a link resides. This means problems arise when this file is updated. Unless the developer making the updates is very aware of what he is doing and changes all the instances of the file, a synchronization problem will occur. Not all instances of the file contain the same information.

These kinds of problems can occur when porting a project (or a set of source files) to a different system, or when you decide to move a project from a local hard disk to a network drive. This is because the network drive you move the project to may use a different file system than your local drive. The easiest way to avoid running into file system problems is to use filenames in your projects which are all in uppercase (or all in lowercase), without any characters outside the alphanumeric range, and which are not too long (eight-character names and three-character extensions.)

Operating System Characteristics

Apart from OS-specific libraries and commands, it is also necessary to take into account operating system behavior, such as the way tasks are switched or how locks perform queuing (refer to Chapter 13 for more details). Because of these kinds of differences the dynamic behavior of multitasking programs can change. A well set up multitasking program should not have too many problems with this; however, it pays to think about how much your program depends on how the OS behaves in certain situations. For instance, you may expect your task to be preempted automatically when waiting for interaction with a slow device or even a screen. This may not be true for the system you are porting to.

Another thing that often differs between operating systems is the way in which they implement text files. First, there is the matter of line termination. Some operating systems expect only a CR (byte value 0x0d ) to indicate where one line ends and another begins. Other operating systems expect a CR and LF (byte values 0x0d and 0x0a ) or only an LF . Secondly, some operating systems use identification characters at the beginning or end of a file (think of Unicode files starting with a special value). This means that no matter how portable you keep your source files, it is possible that a compiler will not even be able to read them. Also, you have to think very carefully when using text files as data storage or configuration files. Moving a project, along with the configuration files used by the original executable, to a new file system or operating system can mean that a newly compiled executable is still able to read the original configuration files, but only as long as they are not opened and edited by hand. When the original text files are opened and edited on the new operating system, new line terminators will be added.

Include Paths

Another thing that can differ per system is whether or not the character is accepted in include path names. Some OSes accept the following include style:

#include " libraries include hi.h"

while others demand the following include style:

#include "/libraries/include/hi.h"

In order to keep your sources portable you should always use the second notation because this is accepted by all compilers.

Hardware Characteristics

When C/C++ sources or projects are ported to systems that use other hardware, issues can arise that are related to hardware characteristics. Think of endianness; an example of this can be found in Chapter 10 in the section on Radix sort. The implementation of Radix sort will compile on any system but it only executes correctly when the algorithm takes the system's endianness into account. Another thing that can differ between types of hardware is the memory size associated with certain variable types. Think of the number of bytes allocated for an int or long. Examples of this can be found in Chapter 6.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset