Using the Standard Header Files to Write Portable C/C++ Applications

August 2011

By Darryl Gove

"The nicest thing about standards is that there are so many of them to choose from."
Computer Networks, 2nd edition, page 254, Andrew S. Tannenbaum

Introduction

System header files define many functions that a program can make use of. The functions that are available, and the way that these functions are declared, depends on the standards that are in force at the time of compilation. It is possible to get compile time errors if the source code relies on a function that is either unavailable under a given specification or has a different declaration. This paper discusses how developers should write and compile programs that depend on functions defined by the many standards supported by Oracle Solaris.

Default Assumptions

The program shown in Listing 1 will compile using the C compiler but not with the C++ compiler.

Listing 1: Example Program
#include <sys/mman.h>

void func(void *addr, size_t len, int prot, int flags,  int
     fildes, off_t off)
{
  mmap(addr,len,prot,flags,fildes,off);
} 

The C++ compiler reports the error message shown in Listing 2.

Listing 2: C++ Error Message
CC -c test.c
"test.c", line 6: Error: Formal argument 1 of type char* in call to 
mmap(char*, unsigned, int, int, int, long) is being passed void*.
1 Error(s) detected. 

The error message complains that a void* pointer is being passed to a function that is expecting a char* parameter. In C, a void* pointer is compatible with a char* pointer, so the C compiler compiles the code without complaint. If the function func() prototype had specified the variable addr as being int*, the C compiler would also have emitted a warning.

The definition from the man page is shown in Listing 3. This matches the way it is used in the code from Listing 1.

Listing 3: Man Page for mmap
$ man mmap

System Calls                                              mmap(2)

NAME
     mmap - map pages of memory

SYNOPSIS
     #include <sys/mman.h>

     void *mmap(void *addr, size_t len, int prot, int flags,  int
     fildes, off_t off);
... 

The source code matches the man page and is, therefore, correct. So the problem is somewhere else. The answer is in how mmap() is defined in the header files. This is shown in Figure 1.

Definition of mmap

Figure 1: Definition of mmap()

We can see in the header files that the definition of mmap() is protected by a couple of #define statements. We get the definition that matches the man page if _XPG4_2 is defined or if _POSIX_C_SOURCE > 2 is true. But we don't get the matching definition by default.

A Choice of Standards

Oracle Solaris documents its support for standards under standards(5). The supported standards are shown with the "feature test macros" in Table 1.

Table 1: Supported Oracle Solaris Standards
Specification Compiler/Flags Feature Test Macros
1989 ANSI C and 1990 ISO C c89 None
1999 ISO C c99 None
1989 SVID3 cc -Xt -xc99=none None
POSIX.1-1990 c89 _POSIX_SOURCE
POSIX.1-1990 and POSIX.2-1992 C-Language Bindings Option c89 _POSIX_SOURCE and POSIX_C_SOURCE=2
POSIX.1b-1993 c89 _POSIX_C_SOURCE=199309L
POSIX.1c-1996 c89 _POSIX_C_SOURCE=199506L
POSIX.1-2001 c99 _POSIX_C_SOURCE=200112L
1990 CAE XPG3 cc -Xa -xc99=none _XOPEN_SOURCE
1992 CAE XPG4 c89 _XOPEN_SOURCE and _XOPEN_VERSION=4
1994 SUS (CAE XPG4v2) (includes XNS4) c89 _XOPEN_SOURCE and _XOPEN_SOURCE_EXTENDED=1
1997 SUSv2 (includes XNS5) c89 _XOPEN_SOURCE=500
2001 SUSv3 c99 XOPEN_SOURCE=600

The feature test macros should defined in by the developer to indicate which standard the source code conforms to. The POSIX and X/OPEN standards require that the developer specify the standard that the source code adheres to. This is the reason that the code shown in Listing 1 gets an error message from C++. The default standard for Oracle Solaris 10 is the System V Interface Definition v3 (SVID3), so the definition of mmap() from the header files uses caddr_t rather than void*.

The POSIX standard states that a strictly conforming application "for the C programming language, shall define _POSIX_C_SOURCE to be 200112L before any header is included."

The other observation to make is that all the standards specify a C compiler, not a C++ compiler. The C++ compiler is not mentioned in the POSIX standards, and the POSIX standards are not mentioned in the C++ standards. Where this can become complex is in the use of the C++ Standard Library, which might be built assuming a particular POSIX standard. The consequence of this is that it is possible for there to be a conflict between the requirements of the application and the requirements of the Standard Library. However, these conflicts are typically resolvable, and the resolution would be in the function prototypes and the availability of certain functions.

Picking the Standard

Table 1 shows the feature test macros that need to be defined in order to conform to the interfaces defined in the various standards supported by Oracle Solaris. Selection of a particular standard causes the header files to provide only the interfaces defined in that standard. As an example of the problems that this might cause, the program in Listing 4 relies on the availability of the function gethrtime().

Listing 4: Application that Relies on Extensions to the Standard
#include <sys/time.h>

double now()
{
  return (double)gethrtime();
} 

This code will not compile if either the POSIX or X/Open feature test macros are defined. This is because the gethrtime() function is not defined by either standard and, therefore, it is considered an extension to the standards. The failed compilation is illustrated in Listing 5 where the code is compiled with _POSIX_C_SOURCE, indicating adherence or conformance to the POSIX.1-1990 standard.

Listing 5: gethrtime() Is Not Available when Compiled to POSIX.1-1990 Standard
$ cc -c -D_POSIX_SOURCE test2.c
"test2.c", line 5: warning: implicit function declaration: gethrtime

To compile code that uses extensions to the standards it is also necessary to define __EXTENSIONS__, as shown in Listing 6. Be aware that defining __EXTENSIONS__ includes prototypes for all extensions not defined by the standards.

Listing 6: Compiling with Extensions Enabled
$ cc -c -D_POSIX_SOURCE -D__EXTENSIONS__ test2.c

One useful compiler option is the flag -H. This tells the compiler to report the header files that are included in a compilation. An example is shown in Listing 7.

Listing 7: Using -H to Output a List of Included Header Files
$ cc -c -H -D_POSIX_SOURCE -D__EXTENSIONS__ test2.c
/usr/include/sys/time.h
        /usr/include/sys/feature_tests.h
                /usr/include/sys/ccompile.h
                /usr/include/sys/isa_defs.h
        /usr/include/sys/types.h
                /usr/include/sys/machtypes.h
                /usr/include/sys/int_types.h
                /usr/include/sys/select.h
                        /usr/include/sys/time_impl.h
                        /usr/include/sys/time.h
        /usr/include/time.h
                /usr/include/iso/time_iso.h 

Using C++ Headers for C Functions

The C++ standard defines a number of header files that are equivalent to the C header files. For example, the C++ header file <cmath> is equivalent to the C header file <math.h>. The principal difference between the two header files is that the C++ standard specifies that the functions declared in the header files should be placed into the std namespace. Listing 8 shows an example program that calls the sin() and cos() functions.

Listing 8: Example of a Function that Calls the Trigonometric Functions
#include <math.h>

float func(float d)
{
  return sin(d)+cos(d);
} 

The code in Listing 8 can be compiled with both the C and C++ compilers. However, when the C <math.h> header is replaced with the C++ standard header <cmath>, the code no longer compiles with the Oracle Solaris Studio C++ compiler, as shown in Listing 9.

Listing 9: Example of Including <cmath>
#include <cmath>
using namespace std;
float func(float d)
{
  return sin(d)+cos(d);
}

$ CC -c m.c
"m.c", line 5: Error: The function "sin" must have a prototype.
"m.c", line 5: Error: The function "cos" must have a prototype.
2 Error(s) detected. 

The errors occur because the functions sin() and cos() are placed in the namespace std. This means that they are inaccessible without additional declarations. There are three ways to fix the problem:

  • Add the qualifier std:: to every use of the functions, to indicate that they will be found in namespace std. For example, sin() becomes std::sin(). The explicit qualifier std:: ensures that no other function called sin will be used for this reference.
  • Introduce individual names into a namespace with a using declaration. For example, the function sin() can be explicitly placed into the global namespace by declaring using std::sin; after including the header. Using this, the standard function becomes visible by normal name look-up.
  • The directive using namespace std; can be added at global scope to tell the compiler to search namespace std for names. This is not a good solution for real applications because it places every declaration in namespace std into the global namespace, which might introduce conflicting definitions.

Note that the original code might compile without error when built using other compilers. This is because not all compilers follow the rule in the C++ standard that the <cyyy> headers place names from <yyy.h> only into the namespace std.

Concluding Remarks

Oracle Solaris supports a large variety of standards. If an application requires a particular implementation of the POSIX standard, that requirement needs to be indicated by the definition of the appropriate feature test macros before the header files are included. In the absence of these definitions, the compiler assumes that the code adheres to the interfaces defined in SVID3.

If an application is coded to a particular standard, but it also uses interfaces that are extensions to that standard, the feature test macro __EXTENSIONS__ also needs to be defined.

Acknowledgements

I am grateful for the help of Steve Clamage, Alan Coopersmith, and Lee Damico, who provided comments and corrections.

Revision 1, 08/03/2011