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
Section 3.6.1: 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
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. |
_XOPEN_SOURCE correspond to Issues 5, 6, and 7 of the X/Open specifications (SUSv2, SUSv3, SUSv4 respectively).| 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. |
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
| 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 |
_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
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
longmay 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 */
| 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 |
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
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 */
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
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 ... */
- 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_SOURCEexposes 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 likeintorlong. - When printing system data types with
printf(), cast tolongand use%ldfor portability. Foroff_t, cast tolong longand use%lld. - Initialize structures using explicit assignments or C99 designated initializers — never use positional initializers for standard UNIX structures.
- Use
#ifdefguards 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.
