Writing Portable C/C++

by Gene Michael Stover

created 1999
updated 2008-04-20T17:25:55

Under construction

What Portable C/C++ Is

At this time, this document is still in a "scrap of notes" or "notes from scraps" form.

It's source code that can compiler on pretty much any Unix system. The more systems, the better. Installation should be simple & seamless.

C++ class istrstream

I like to check the state of an I/O stream (i.e., an instance of istream, ostream, iostream, or any of their subclasses) after I/O operations to ensure that things worked. Sometimes this wasn't just detail-minded, it was necessary. (Implementing a parser is such a case.) I generally prefer explicit function calls to implicit type-casts or constructors, & I also prefer to check for success, so I most often used message good to ask a input stream if an input operation succeeded. In other words, I did this:

int i;
istrstream strm ("123");
strm >> i;
if (strm.good ())
  {
    if (i == 123)
      {
        cout << "Good.  The integer is " << i << "." << endl;
      }
    else
      {
        cout << "Failure." << endl;
      }

This worked fine on Linux using gcc 2.8.1 & libstdc++ 2.8.1.1, but it failed on HP/UX 10.20 aCC of unknown version. On HP/UX, the strm.good () test failed. Interesting, that.

Some research revealed that, on Linux with gcc, the stream's state after the input operation was "good"; in other words, no bits were set in its state. On HP/UX, the stream's state was "eof" in other words, the ios::eofbit bit was set.

Why the difference? Which implementation was correct?

Variable Arguments

If your compiler is Standard C, use the macros from stdarg.h. Note that, in this set of macros, va_start requires two arguments.

If your compiler is old C, use the macros from varargs.h. Note that, in this set of macros, va_start requires one argument. Also note that two special symbols, va_alist & va_dcl, are used when defining the function.

So do this:

  #if __STDC__
  # include <stdarg.h>
  #else
  # include <varargs.h>
  #endif

  #if __STDC__
  int f00 (int i, ...);
  #else
  int foo (/* int i, ... */);
  #endif

  int
  #if __STDC__
  foo (int i, ...)
  #else
  foo (i, va_alist)
       int i;
       va_dcl
  #endif
  {
    va_list va;

  #if __STDC__
    va_start (va, i);
  #else
    va_start (va);
  #endif

    ...
    ..

    va_end (va);
  }

It's possible to write a set of macros that hide the different versions of va_start, but it's impossible (?) to write macros that hide the different function declarations. So I don't think it's worth the effort of hiding the different va_start macros.

I found this problem while porting some of my test tools at Metapath to HP/UX. I had been using stdarg.h & the macros in it. On HP/UX, when I compiled with the native compiler in old C mode (which I needed for other portability reasons), I found that the first time a function used the va_arg macro, it retrieved the first argument. In other words, if I had used "va_start (va, i)", then the first time I used va_arg, my function retrieved the value of i.

Much research \& thought, combined with attention to the warnings from Standard C compilers that I was using va_start in functions that don'thave variable argument lists, revealed that the problem was in the va_... macros. Using the appropriate set of macros for the compiler (Standard or non-standard) fixes the problem.

I previously thought that stdarg.h differed from varargs.h only to be a nuissance. This experience revealed that the differences are necessary. (Or at least, they are for a purpose.) I still don't know why the macros must match the comiler. The best I figure is simply that the {\tt __builtin_va_start} function is tailored to the compiler's mode (Standard or non-standard). \section{Formal Argument Type Promotions} On {\tt pdu4b.etl}, a Lynx machine with an old version of the Gnu GCC compiler, the compiler spewed this error message: {\tt \begin{poetry} test0063.c: In function S_Parent: test0063.c:454: argument `childPid' doesn't match function prototype test0063.c:454: a formal parameter type that promotes to `int' test0063.c:454: can match only `int' in the prototype make: *** [test0063] Error 1 \end{poetry} } The offending chunk of code is this: {\tt \begin{poetry} static int S_Parent (p, childPid) int p[2]; pid_t childPid; { ... } \end{poetry} }. The compiler is not Standard C, so it did not compile the function prototype, which is this: {\tt \begin{poetry} static int S_Parent (int p[2], pid_t childPid); \end{poetry} }. Instead, it compiled a plain-old function declaration. My first thought was that the Lynx GCC header files did not define or {\tt typedef} ``pid\_t''. A grep showed this was not so. The file {\tt types.h} contains these lines (among other, of course): {\tt \begin{poetry} types.h: * Added mode_t, nlink_t, and pid_t for POSIX compliance types.h:typedef short pid_t; /* short so POSIX can use System V flock structure */ \end{poetry} } Hmmm\ldots {\tt pid\_t} is defined, in fact, it's {\tt short}, \& the error message said something about types that promote to {\tt int}. {\tt short} promotes to {\tt int}. So that's the problem. Since the compiler doesn't know about function prototypes, it has to use old-fashioned rules for promoting function arguments. One of those rules is that a short is promoted to {\tt int} before it's pushed on the stack as an argument to a function. In such a system, no function can ever receive a short as an actual argument. The compiler is warning me about this. Changing the type of the childPid argument from pid_t to int fixed the problem. I see two important meanings from this:

  1. When you define your own scalar types, you should ensure that they are targets in the type-promotion rules used by compilers that don't understand function prototypes. In other words, they whould be int, double, or pointer. (Structures don't count here, because you should never send a structure as an argument, anyway.)
  2. When using types that may have been declared by the native system, you should declare functions that have formal arguments of that type. Instead, you should have a formal argument of type int or double, or a pointer to the argument. This is unfortunate, but it's portable.

End.