5

Currently I'm working on a C/C++ code-base which is fairly portable, it can compile on most Unix like systems as well as MS-Windows (MSVC), using various popular compilers.

Previously I've found testing on different OS's and architectures can help find obscure bugs or bad assumptions.

I worry with the dominance of x86/amd64 our code-base may unknowingly become less portable.

Besides testing on a big-endian system (to find obvious errors with big/little endian), are there some architectures which have characteristics making them better for stress-testing software portability?

Examples of possible differences.

  • different endian.
  • different behavior when threading.
  • behavior of stack memory.
  • size of primitive types (char, short, int, long, float... etc).
  • alignment/padding of structs (which might hide errors).
  • difference in optimizations made by the compiler.

Are there some architectures which have more significant differences to x86/amd64, making them better candidates for exposing code portability issues? (and have C/C++ compilers and libraries - libc, libstdc++).

Asking because its a sizable time-investment to setup a new system, even if its emulated.


in case its not clear what I mean by processor-architectures, eg (x86, amd64, ia64, mips, risc, arm, m68k, ppc, itanium)


Note, I'm not proposing this as a primary way to discover bugs, we run multiple static analysis tools and tests, but in the past we have found errors in code because of differences in less common platforms (SGI, SPARC, Solaris, BSD's etc. However some of these systems are fading out of use)

6

You are right that using different setups for your tests can increase the chance of accidentally stumbling on some bugs. However, you should consider whether setting up a another testing rig makes sense from a business perspective of things – I suspect you want to sell or distribute useful code, rather than crafting The Perfect Code in your ivory tower.

I worry with the dominance of X86/AMD64 our code-base may unknowingly become less portable.

If your code only ever runs on x86 or AMD64 architectures, then there is little use for testing on other architectures – YAGNI applies here. You'd be better off by expanding your test suite to guarantee all documented behaviour, and by purging the code base of dubious constructs with the help of linters. Using multiple different compilers on different settings is also a low-cost, high-impact strategy for discovering bugs (e.g. GCC and Clang).

If however you explicitly support certain combinations of architectures and operating systems, you should also test those combinations.

If you nevertheless do want to test some more alien setups that are still used fairly commonly, I would recommend:

  • Architecture: SPARC. OSes: Solaris family, Linux, BSD family. Endianness: big, possibly bi. Comments: massive parallelism.

  • Architecture: ARM. OSes: Linux, BSD family, OpenSolaris. Endianness: little, possibly bi. Comments: used in embedded devices, mobile phones.

The features you listed can be tested by varying the following components in the setup:

  • Endianess: architecture.
  • Threading: OS, kernel settings, threading libraries, number of processors.
  • Stack: ?
  • Primitive sizes: preprocessor directives, compiler settings.
  • Struct alignment: compilers.
  • Optimizations: compiler settings.
  • Other: compile using different libc implementations.

My primary exposure to cross-platform programming is reading through the source of the Perl interpreter. Here portability issues are adressed by:

  • … detecting features provided by the used libc implementation, possibly substituting custom functions. This information is then recorded as a set of preprocessor definitions before compilation.
  • … pervasively using macros for numeric types. The sizes can be set during compilation.
  • … making threading optional, as the unthreaded version performs better. In the code, some sections are executed only when compiled with threading support.
  • … mostly ignoring endianess, as this tends to sort itself out. Endianness only becomes relevant when doing something like interpreting a given bit pattern as a 16-bit BE number on a LE system.
  • … explicitly listing supported platforms and also documenting portability issues or differing behaviour on these platforms.
  • … running a huge test suite incl. a ton of regression tests that are executed on a network of CI boxes.
  • Thanks for the answer, - I meant amd64, not ia64 - my bad :S – ideasman42 Aug 3 '14 at 10:42
  • re: mostly ignoring endianess - worth mentioning file formats (which is mostly where we deal with it), realize you're talking about your own exposure though. – ideasman42 Aug 3 '14 at 11:24
  • 3
    Testing on non-x86/AMD64 (particularly ARM) can also reveal alignment errors, because the ARM processor gives an error on unaligned access, where the x86 just slows down. – Bart van Ingen Schenau Aug 3 '14 at 18:17
4

I think you've probably covered much of this already, but I'd give some further consideration to:

  • Low memory environment, e.g. mobile with small RAM or embedded devices. While your current customers may not need this, who knows if your library is suddenly in huge demand for engine management systems or home routers.
  • Highly concurrent environment, e.g. more threads and cores than you've ever considered possible. While you may have single-threaded and multi-threaded code paths as a compile option, it's possible that a massive number of threads may expose contention you didn't know you had.
  • Can I supply my own allocators? If not, what happens if I redefine malloc or operator new() to use a slab allocator or otherwise non-standard memory mechanism - can your code still cope?
  • Are you relying on initialisation order anywhere? Any statically-allocated variables?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.