tags:

views:

493

answers:

8

The following is an excerpt from Bjarne Stroustrup's book, The C++ Programming Language:

Section 4.6:

Some of the aspects of C++’s fundamental types, such as the size of an int, are implementation- defined (§C.2). I point out these dependencies and often recommend avoiding them or taking steps to minimize their impact. Why should you bother? People who program on a variety of systems or use a variety of compilers care a lot because if they don’t, they are forced to waste time finding and fixing obscure bugs. People who claim they don’t care about portability usually do so because they use only a single system and feel they can afford the attitude that ‘‘the language is what my compiler implements.’’ This is a narrow and shortsighted view. If your program is a success, it is likely to be ported, so someone will have to find and fix problems related to implementation-dependent features. In addition, programs often need to be compiled with other compilers for the same system, and even a future release of your favorite compiler may do some things differently from the current one. It is far easier to know and limit the impact of implementation dependencies when a program is written than to try to untangle the mess afterwards.

It is relatively easy to limit the impact of implementation-dependent language features.

My question is: How to limit the impact of implementation-dependent language features? Please mention implementation-dependent language features then show how to limit their impact.

Thanks in advance.

+4  A: 

Well, the variable sizes one mentioned is a fairly well known issue, with the common workaround of providing typedeffed versions of the basic types that have well defined sizes (normally advertised in the typedef name). This is done use preprocessor macros to give different code-visibility on different platforms. E.g.:

#ifdef __WIN32__
typedef int int32;
typedef char char8;
//etc
#endif
#ifdef __MACOSX__
//different typedefs to produce same results
#endif

Other issues are normally solved in the same way too (i.e. using preprocessor tokens to perform conditional compilation)

workmad3
Or you could design you code so that it doesn't care what the actual sizes are. Maybe add an assert to make sure that they are big enough. Unless you are interfacing with low-level drivers, most code can be made so that it is independant of extact sizes.
KeithB
The sizes aren't normally an issue with well written code, true... but the ranges frequently are. You could use asserts and things like std::numerical_limits, or you can use typedeffed things like above and have well defined sizes and ranges for 'standard' types :)
workmad3
+2  A: 

A good solution is to use common headings that define typedeff'ed types as neccessary.

For example, including sys/types.h is an excellent way to deal with this, as is using portable libraries.

Arafangion
+3  A: 

The most obvious implementation dependency is size of integer types. There are many ways to handle this. The most obvious way is to use typedefs to create ints of the various sizes:

 typedef signed   short  int16_t;
 typedef unsigned short  uint16_t;

The trick here is to pick a convention and stick to it. Which convention is the hard part: INT16, int16, int16_t, t_int16, Int16, etc. C99 has the stdint.h file which uses the int16_t style. If your compiler has this file, use it.

Similarly, you should be pedantic about using other standard defines such as size_t, time_t, etc.

The other trick is knowing when not to use these typedef. A loop control variable used to index an array, should just take raw int types so the compile will generate the best code for your processor. for (int32_t i = 0; i < x; ++i) could generate a lot of needless code on a 64-bite processor, just like using int16_t's would on a 32-bit processor.

jmucchiello
In fact, the best type to use for iterating through an array on any platform where stdint.h is available will be offset_t. that type is always the correct type for pointer arithmatic
TokenMacGuy
I knew there was type for that but wasn't sure what. size_t made some sense but not enough sense :) Thanks for that info.
jmucchiello
+1  A: 

One of the key ways of avoiding dependancy on particular data sizes is to read & write persistent data as text, not binary. If binary data must be used then all read/write operations must be centralised in a few methods and approaches like the typedefs already described here used.

A second rhing you can do is to enable all your your compilers warnings. for example, using the -pedantic flag with g++ will warn you of lots of potential portability problems.

anon
Actually, to modularise your code is a good practice, not limited to using binary data.
Arafangion
anon
A: 

If you're concerned about portability, things like the size of an int can be determined and dealt with without much difficulty. A lot of C++ compilers also support C99 features like the int types: int8_t, uint8_t, int16_t, uint32_t, etc. If yours doesn't support them natively, you can always include <cstdint> or <sys/types.h>, which, more often than not, has those typedefed. <limits.h> has these definitions for all the basic types.

The standard only guarantees the minimum size of a type, which you can always rely on: sizeof(char) < sizeof(short) <= sizeof(int) <= sizeof(long). char must be at least 8 bits. short and int must be at least 16 bits. long must be at least 32 bits.

Other things that might be implementation-defined include the ABI and name-mangling schemes (the behavior of export "C++" specifically), but unless you're working with more than one compiler, that's usually a non-issue.

greyfade
+4  A: 

Few ideas:

  • Unfortunately you will have to use macros to avoid some platform specific or compiler specific issues. You can look at the headers of Boost libraries to see that it can quite easily get cumbersome, for example look at the files:

  • The integer types tend to be messy among different platforms, you will have to define your own typedefs or use something like Boost cstdint.hpp

  • If you decide to use any library, then do a check that the library is supported on the given platform

  • Use the libraries with good support and clearly documented platform support (for example Boost)

  • You can abstract yourself from some C++ implementation specific issues by relying heavily on libraries like Qt, which provide an "alternative" in sense of types and algorithms. They also attempt to make the coding in C++ more portable. Does it work? I'm not sure.

  • Not everything can be done with macros. Your build system will have to be able to detect the platform and the presence of certain libraries. Many would suggest autotools for project configuration, I on the other hand recommend CMake (rather nice language, no more M4)

  • endianness and alignment might be an issue if you do some low level meddling (i.e. reinterpret_cast and friends things alike (friends was a bad word in C++ context)).

  • throw in a lot of warning flags for the compiler, for gcc I would recommend at least -Wall -Wextra. But there is much more, see the documentation of the compiler or this question.

  • you have to watch out for everything that is implementation-defined and implementation-dependend. If you want the truth, only the truth, nothing but the truth, then go to ISO standard.

Anonymous
Nice answer. More stuff than I was aware of :)
workmad3
+2  A: 

There are two approaches to this:

  • define your own types with a known size and use them instead of built-in types (like typedef int int32 #if-ed for various platforms)
  • use techniques which are not dependent on the type size

The first is very popular, however the second, when possible, usually results in a cleaner code. This includes:

  • do not assume pointer can be cast to int
  • do not assume you know the byte size of individual types, always use sizeof to check it
  • when saving data to files or transferring them across network, use techniques which are portable across changing data sizes (like saving/loading text files)

One recent example of this is writing code which can be compiled for both x86 and x64 platforms. The dangerous part here is pointer and size_t size - be prepared it can be 4 or 8 depending on platform, when casting or differencing pointer, cast never to int, use intptr_t and similar typedef-ed types instead.

Suma
A: 

The following is also an excerpt from Bjarne Stroustrup's book, The C++ Programming Language:

Section 10.4.9:

No implementation-independent guarantees are made about the order of construction of nonlocal objects in different compilation units. For example:

// file1.c:
 Table tbl1;
// file2.c:
 Table tbl2;

Whether tbl1 is constructed before tbl2 or vice versa is implementation-dependent. The order isn’t even guaranteed to be fixed in every particular implementation. Dynamic linking, or even a small change in the compilation process, can alter the sequence. The order of destruction is similarly implementation-dependent.

A programmer may ensure proper initialization by implementing the strategy that the implementations usually employ for local static objects: a first-time switch. For example:

class Zlib {
 static bool initialized;
 static void initialize() { /* initialize */ initialized = true; }
public:
 // no constructor

 void f()
 {
  if (initialized == false) initialize();
  // ...
 }
 // ...
};

If there are many functions that need to test the first-time switch, this can be tedious, but it is often manageable. This technique relies on the fact that statically allocated objects without constructors are initialized to 0. The really difficult case is the one in which the first operation may be time-critical so that the overhead of testing and possible initialization can be serious. In that case, further trickery is required (§21.5.2).

An alternative approach for a simple object is to present it as a function (§9.4.1):

int& obj() { static int x = 0; return x; } // initialized upon first use

First-time switches do not handle every conceivable situation. For example, it is possible to create objects that refer to each other during construction. Such examples are best avoided. If such objects are necessary, they must be constructed carefully in stages.

Bill