Portability in System Programming

 

Portability in System Programming
Feature Test Macros, System Data Types, and Miscellaneous Portability Issues
3.6
Chapter Section
5+
Feature Test Macros
30+
System Data Types

Why Portability Matters

Writing a system program that works correctly only on one specific UNIX implementation is of limited value. Portable programs can run on any standards-conformant system, reaching wider audiences and remaining useful as hardware and operating systems evolve.

Portability challenges in system programming come from two main sources: differences in the behavior of APIs across implementations, and differences in the sizes of data types used to represent system information. This section covers the tools Linux provides to address both issues.

Key Concepts

Feature Test Macros _POSIX_SOURCE _POSIX_C_SOURCE _XOPEN_SOURCE _GNU_SOURCE _BSD_SOURCE SUSv3 pid_t uid_t off_t ssize_t struct sembuf

Section 3.6.1: Feature Test Macros

What are Feature Test Macros?

Various standards govern the behavior of system call and library function APIs. Sometimes, when writing a portable application, you want header files to expose only the definitions that follow a particular standard — constants, function prototypes, and so on. Feature test macros allow you to control which definitions the header files make visible.

The name “feature test macro” makes sense from the implementation’s perspective: the header file implementation decides which features to make visible by testing (with #if) which values the application has defined for these macros.

There are two ways to define feature test macros:

/* Method 1: Define in source code before any header includes */
#define _BSD_SOURCE 1

#include <stdio.h>
/* ... rest of includes ... */

/* Method 2: Pass -D flag to the C compiler */
$ cc -D_BSD_SOURCE prog.c

/* Multiple macros can be combined */
$ cc -D_POSIX_SOURCE -D_POSIX_C_SOURCE=199506 \
     -D_BSD_SOURCE -D_SVID_SOURCE prog.c

Standards-Specified Feature Test Macros
POSIX Conformant Portable Across Systems

The following macros are specified by the relevant standards and are portable to all systems that support these standards:

Macro Effect When Defined
_POSIX_SOURCE Expose definitions conforming to POSIX.1-1990 and ISO C (1990). Superseded by _POSIX_C_SOURCE.
_POSIX_C_SOURCE = 1 Same effect as _POSIX_SOURCE.
_POSIX_C_SOURCE >= 199309 Also expose POSIX.1b (realtime) definitions.
_POSIX_C_SOURCE >= 199506 Also expose POSIX.1c (threads) definitions.
_POSIX_C_SOURCE = 200112 Also expose POSIX.1-2001 base specification (XSI extension excluded).
_POSIX_C_SOURCE = 200809 Also expose POSIX.1-2008 base specification.
_XOPEN_SOURCE (any value) Expose POSIX.1, POSIX.2, and X/Open (XPG4) definitions.
_XOPEN_SOURCE >= 500 Also expose SUSv2 (UNIX 98 and XPG5) extensions.
_XOPEN_SOURCE = 600 Also expose SUSv3 XSI (UNIX 03) extensions and C99 extensions. Full SUSv3 compliance.
_XOPEN_SOURCE = 700 Also expose SUSv4 XSI extensions. Full SUSv4 compliance.
Values Explained: The values 500, 600, and 700 for _XOPEN_SOURCE correspond to Issues 5, 6, and 7 of the X/Open specifications (SUSv2, SUSv3, SUSv4 respectively).

glibc-Specific Feature Test Macros
Linux Only Not Portable
Macro Effect When Defined
_BSD_SOURCE Expose BSD definitions. Also defines _POSIX_C_SOURCE = 199506. BSD definitions favored where standards conflict.
_SVID_SOURCE Expose System V Interface Definition (SVID) definitions.
_GNU_SOURCE Expose everything — all definitions from all preceding macros, plus various GNU extensions. Most permissive option.

Default Compiler Behavior

When the GNU C compiler is invoked without special options, the following macros are defined by default:

  • _POSIX_SOURCE
  • _POSIX_C_SOURCE = 200809 (200112 with glibc 2.5–2.9; 199506 with earlier glibc)
  • _BSD_SOURCE
  • _SVID_SOURCE

If individual macros are defined, or the compiler is invoked in one of its standard modes (e.g., cc –ansi or cc –std=c99), then only the requested definitions are supplied.

/* Standard compilation — uses glibc default macros */
$ cc prog.c

/* ANSI/C99 standard mode — only minimum definitions exposed */
$ cc -std=c99 prog.c

/* Example programs in TLPI compile with one of these options: */
$ cc prog.c                         /* Default GNU C compiler options */
$ cc -std=c99 -D_XOPEN_SOURCE=600 prog.c  /* SUSv3 (XSI) conformance */

/* Defining multiple macros is additive: */
$ cc -D_POSIX_SOURCE -D_POSIX_C_SOURCE=199506 \
     -D_BSD_SOURCE -D_SVID_SOURCE prog.c

SUSv3 and SUSv4 Conformance Settings
Standard _POSIX_C_SOURCE value _XOPEN_SOURCE value Conformance Level
POSIX.1-2001 (SUSv3 base) 200112 POSIX base, no XSI extension
SUSv3 (full XSI) — (implied by XSI) 600 Full SUSv3: base + XSI extension
POSIX.1-2008 (SUSv4 base) 200809 POSIX base, no XSI extension
SUSv4 (full XSI) — (implied by XSI) 700 Full SUSv4: base + XSI extension
Key Rule: Setting _XOPEN_SOURCE = 600 supplies all features enabled by _POSIX_C_SOURCE = 200112. For SUSv3 conformance, defining only _XOPEN_SOURCE = 600 is sufficient.

Section 3.6.2: System Data Types

Why System Data Types Are Needed

Various implementation data types — such as process IDs, user IDs, and file offsets — are represented using standard C types. Although it would be possible to use fundamental C types such as int and long directly, this reduces portability for two reasons:

  • The sizes of fundamental C types vary across UNIX implementations (e.g., a long may be 4 bytes on one system and 8 bytes on another), or sometimes even between compilation environments on the same system.
  • On a single UNIX implementation, the types used to represent information may differ between releases. A notable example: on Linux 2.2 and earlier, user and group IDs were 16-bit values. On Linux 2.4 and later, they became 32-bit values.

To solve this, SUSv3 specifies a range of standard system data types and requires implementations to define and use these types appropriately. Most have names ending in _t and are declared in <sys/types.h> or other header files.

#include <sys/types.h>

/* On Linux/x86-32, pid_t is defined as: */
typedef int pid_t;

/* Use the typedef in application code for portability */
pid_t mypid;
mypid = getpid();   /* Returns process ID of calling process */

/* Correct way to print system data types */
printf("My PID is %ld\n", (long) mypid);
/* Always cast to long and use %ld — the underlying type may be
   int or long depending on the platform */

Selected System Data Types (SUSv3)
Data Type SUSv3 Requirement Description
blkcnt_t signed integer File block count
blksize_t signed integer File block size
clock_t integer or real-floating System time in clock ticks
dev_t arithmetic type Device number (major + minor)
fd_set structure type File descriptor set for select()
gid_t integer Numeric group identifier
id_t integer Generic ID type; large enough for pid_t, uid_t, gid_t
ino_t unsigned integer File i-node number
mode_t integer File permissions and type
nlink_t integer Count of hard links to a file
off_t signed integer File offset or size
pid_t signed integer Process ID, process group ID, or session ID
ptrdiff_t signed integer Difference between two pointer values
rlim_t unsigned integer Resource limit
sig_atomic_t integer Data type for atomic access
sigset_t integer or structure Signal set
size_t unsigned integer Size of an object in bytes
socklen_t integer (at least 32 bits) Size of a socket address structure
ssize_t signed integer Byte count or negative error indication
suseconds_t signed integer [-1, 1000000] Microsecond time interval
time_t integer or real-floating Calendar time in seconds since the Epoch
uid_t integer Numeric user identifier

Printing System Data Types with printf()

When printing values of numeric system data types in printf(), you must avoid representation dependency. Because C’s argument promotion rules convert short to int but leave int and long unchanged, the underlying native type of a system data type affects what is passed to printf().

The standard solution is to use the %ld specifier and always cast the value to long:

pid_t mypid;
mypid = getpid();
printf("My PID is %ld\n", (long) mypid);
/* Works regardless of whether pid_t is int or long on this platform */

uid_t myuid = getuid();
printf("My UID is %ld\n", (long) myuid);

/* Special case: off_t may be long long in some environments */
off_t filesize;
printf("File size: %lld bytes\n", (long long) filesize);
/* Use %lld and cast to long long for off_t */

/* Note: C99 defines %zd for size_t/ssize_t and %jd for intmax_t,
   but these are not available on all UNIX implementations.
   Prefer (long) cast + %ld for maximum portability. */

Section 3.6.3: Miscellaneous Portability Issues

Initializing Structures Portably
struct sembuf Field Order C99 Initializers

Standard structures used in system calls and library functions may have fields defined in a different order on different UNIX implementations. They may also contain additional implementation-specific fields. This makes positional structure initializers non-portable.

/* struct sembuf — used with semop() system call */
struct sembuf {
    unsigned short sem_num;  /* Semaphore number */
    short          sem_op;   /* Operation to be performed */
    short          sem_flg;  /* Operation flags */
};

/* NON-PORTABLE: Positional initializer — field order may differ */
struct sembuf s = { 3, -1, SEM_UNDO };  /* Works on Linux, not portable */

/* PORTABLE Method 1: Explicit assignment statements */
struct sembuf s;
s.sem_num = 3;
s.sem_op  = -1;
s.sem_flg = SEM_UNDO;

/* PORTABLE Method 2: C99 designated initializers */
struct sembuf s = { .sem_num = 3, .sem_op = -1, .sem_flg = SEM_UNDO };
/* C99 designated initializers are order-independent */
File Writing: The same consideration applies if you want to write the contents of a standard structure to a file. To do this portably, you cannot simply do a binary write of the entire structure. Instead, write structure fields individually (typically in text form) in a specified, documented order.
Macros Not Present on All Implementations
WCOREDUMP() #ifdef Guard Conditional Compilation

Some macros may not be defined on all UNIX implementations. For example, the WCOREDUMP() macro (which checks whether a child process produced a core dump file) is widely available but is not specified in SUSv3, so it might not exist on some platforms.

/* Use #ifdef to handle macros not present on all implementations */
#ifdef WCOREDUMP
    if (WCOREDUMP(status))
        printf("Child produced a core dump\n");
#endif

/* Similarly, protect glibc-specific features */
#ifdef _GNU_SOURCE
    /* GNU extension code here */
#endif
Variation in Required Header Files

The header files required to prototype various system calls and library functions vary across UNIX implementations. Some function synopses include a header file with the comment /* For portability */, indicating that although Linux does not require it, some other implementations may.

/* POSIX.1-1990 required <sys/types.h> first for many functions.
   Although modern systems don't need it, include it first in
   portable programs as a precaution. */

#include <sys/types.h>   /* For portability — not required on Linux */
#include <unistd.h>
#include <stdio.h>
/* ... rest of program ... */

Chapter 3 Summary – All Portability Guidelines
Feature Test Macros System Data Types Portable Structures
  • Use feature test macros to control which API definitions are visible from header files, ensuring conformance to specific standards.
  • For SUSv3 (XSI) conformance, define _XOPEN_SOURCE = 600. For SUSv4, use _XOPEN_SOURCE = 700.
  • _GNU_SOURCE exposes all GNU extensions plus everything from all standard macros — the most permissive option.
  • Use system data types (pid_t, uid_t, off_t, size_t, etc.) rather than native C types like int or long.
  • When printing system data types with printf(), cast to long and use %ld for portability. For off_t, cast to long long and use %lld.
  • Initialize structures using explicit assignments or C99 designated initializers — never use positional initializers for standard UNIX structures.
  • Use #ifdef guards around macros that may not exist on all implementations.
  • Consider including <sys/types.h> first in portable programs for maximum compatibility.

Chapter 3 Complete

You have covered all topics in Chapter 3: System Programming Concepts.

Review: System Calls Review: Error Handling

Leave a Reply

Your email address will not be published. Required fields are marked *