| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
C and C++ programs often use low-level features of the underlying system, and therefore are often more difficult to make portable to other platforms.
Several standards have been developed to help make your programs more portable. If you write programs with these standards in mind, you can have greater confidence that your programs work on a wide variety of systems. Language Standards Supported by GCC for a list of C-related standards. Many programs also assume the Posix standard.
Some old code is written to be portable to K&R C, which predates any C standard. K&R C compilers are no longer of practical interest, though, and the rest of section assumes at least C89, the first C standard.
Program portability is a huge topic, and this section can only briefly introduce common pitfalls. See (standards)System Portability section `Portability between System Types' in GNU Coding Standards, for more information.
| 13.1 Varieties of Unportability | How to make your programs unportable | |
| 13.2 Integer Overflow | When integers get too large | |
| 13.3 Preprocessor Arithmetic | #if expression problems
| |
| 13.4 Properties of Null Pointers | Properties of null pointers | |
| 13.5 Buffer Overruns and Subscript Errors | Subscript errors and the like | |
| 13.6 Volatile Objects | volatile and signals
| |
| 13.7 Floating Point Portability | Portable floating-point arithmetic | |
| 13.8 Exiting Portably | Exiting and the exit status |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Autoconf tests and ordinary programs often need to test what is allowed on a system, and therefore they may need to deliberately exceed the boundaries of what the standards allow, if only to see whether an optional feature is present. When you write such a program, you should keep in mind the difference between constraints, unspecified behavior, and undefined behavior.
In C, a constraint is a rule that the compiler must enforce. An example constraint is that C programs must not declare a bit-field with negative width. Tests can therefore reliably assume that programs with negative-width bit-fields are rejected by a compiler that conforms to the standard.
Unspecified behavior is valid behavior, where the standard allows multiple possibilities. For example, the order of evaluation of function arguments is unspecified. Some unspecified behavior is implementation-defined, i.e., documented by the implementation, but since Autoconf tests cannot read the documentation they cannot distinguish between implementation-defined and other unspecified behavior. It is common for Autoconf tests to probe implementations to determine otherwise-unspecified behavior.
Undefined behavior is invalid behavior, where the standard allows the implementation to do anything it pleases. For example, dereferencing a null pointer leads to undefined behavior. If possible, test programs should avoid undefined behavior, since a program with undefined behavior might succeed on a test that should fail.
The above rules apply to programs that are intended to conform to the standard. However, strictly-conforming programs are quite rare, since the standards are so limiting. A major goal of Autoconf is to support programs that use implementation features not described by the standard, and it is fairly common for test programs to violate the above rules, if the programs work well enough in practice.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In practice many portable C programs assume that signed integer overflow wraps around reliably using two's complement arithmetic. Yet the C standard says that program behavior is undefined on overflow, and in a few cases C programs do not work on some modern implementations because their overflows do not wrap around as their authors expected. Conversely, in signed integer remainder, the C standard requires overflow behavior that is commonly not implemented.
| 13.2.1 Basics of Integer Overflow | Why integer overflow is a problem | |
| 13.2.2 Examples of Code Assuming Wraparound Overflow | Examples of code assuming wraparound | |
| 13.2.3 Optimizations That Break Wraparound Arithmetic | Optimizations that break uses of wraparound | |
| 13.2.4 Practical Advice for Signed Overflow Issues | Practical advice for signed overflow issues | |
| 13.2.5 Signed Integer Division and Integer Overflow | INT_MIN / -1 and INT_MIN % -1
|
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In languages like C, unsigned integer overflow reliably wraps around;
e.g., UINT_MAX + 1 yields zero.
This is guaranteed by the C standard and is
portable in practice, unless you specify aggressive,
nonstandard optimization options
suitable only for special applications.
In contrast, the C standard says that signed integer overflow leads to undefined behavior where a program can do anything, including dumping core or overrunning a buffer. The misbehavior can even precede the overflow. Such an overflow can occur during addition, subtraction, multiplication, division, and left shift.
Despite this requirement of the standard, many C programs and Autoconf tests assume that signed integer overflow silently wraps around modulo a power of two, using two's complement arithmetic, so long as you cast the resulting value to a signed integer type or store it into a signed integer variable. If you use conservative optimization flags, such programs are generally portable to the vast majority of modern platforms, with a few exceptions discussed later.
For historical reasons the C standard also allows implementations with ones' complement or signed magnitude arithmetic, but it is safe to assume two's complement nowadays.
Also, overflow can occur when converting an out-of-range value to a signed integer type. Here a standard implementation must define what happens, but this might include raising an exception. In practice all known implementations support silent wraparound in this case, so you need not worry about other possibilities.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There has long been a tension between what the C standard requires for signed integer overflow, and what C programs commonly assume. The standard allows aggressive optimizations based on assumptions that overflow never occurs, but many practical C programs rely on overflow wrapping around. These programs do not conform to the standard, but they commonly work in practice because compiler writers are understandably reluctant to implement optimizations that would break many programs, unless perhaps a user specifies aggressive optimization.
The C Standard says that if a program has signed integer overflow its behavior is undefined, and the undefined behavior can even precede the overflow. To take an extreme example:
if (password == expected_password)
allow_superuser_privileges ();
else if (counter++ == INT_MAX)
abort ();
else
printf ("%d password mismatches\n", counter);
|
If the int variable counter equals INT_MAX,
counter++ must overflow and the behavior is undefined, so the C
standard allows the compiler to optimize away the test against
INT_MAX and the abort call.
Worse, if an earlier bug in the program lets the compiler deduce that
counter == INT_MAX or that counter previously overflowed,
the C standard allows the compiler to optimize away the password test
and generate code that allows superuser privileges unconditionally.
Despite this requirement by the standard, it has long been common for C code to assume wraparound arithmetic after signed overflow, and all known practical C implementations support some C idioms that assume wraparound signed arithmetic, even if the idioms do not conform strictly to the standard. If your code looks like the following examples it will almost surely work with real-world compilers.
Here is an example derived from the 7th Edition Unix implementation of
atoi (1979-01-10):
char *p; int f, n; … while (*p >= '0' && *p <= '9') n = n * 10 + *p++ - '0'; return (f ? -n : n); |
Even if the input string is in range, on most modern machines this has
signed overflow when computing the most negative integer (the -n
overflows) or a value near an extreme integer (the first +
overflows).
Here is another example, derived from the 7th Edition implementation of
rand (1979-01-10). Here the programmer expects both
multiplication and addition to wrap on overflow:
static long int randx = 1; … randx = randx * 1103515245 + 12345; return (randx >> 16) & 077777; |
In the following example, derived from the GNU C Library 2.5
implementation of mktime (2006-09-09), the code assumes
wraparound arithmetic in + to detect signed overflow:
time_t t, t1, t2;
int sec_requested, sec_adjustment;
…
t1 = t + sec_requested;
t2 = t1 + sec_adjustment;
if (((t1 < t) != (sec_requested < 0))
| ((t2 < t1) != (sec_adjustment < 0)))
return -1;
|
If your code looks like these examples, it is probably safe even though it does not strictly conform to the C standard. This might lead one to believe that one can generally assume wraparound on overflow, but that is not always true, as can be seen in the next section.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Compilers sometimes generate code that is incompatible with wraparound
integer arithmetic. A simple example is an algebraic simplification: a
compiler might translate (i * 2000) / 1000 to i * 2
because it assumes that i * 2000 does not overflow. The
translation is not equivalent to the original when overflow occurs:
e.g., in the typical case of 32-bit signed two's complement wraparound
int, if i has type int and value 1073742,
the original expression returns -2147483 but the optimized
version returns the mathematically correct value 2147484.
More subtly, loop induction optimizations often exploit the undefined
behavior of signed overflow. Consider the following contrived function
sumc:
int
sumc (int lo, int hi)
{
int sum = 0;
int i;
for (i = lo; i <= hi; i++)
sum ^= i * 53;
return sum;
}
|
To avoid multiplying by 53 each time through the loop, an optimizing
compiler might internally transform sumc to the equivalent of the
following:
int
transformed_sumc (int lo, int hi)
{
int sum = 0;
int hic = hi * 53;
int ic;
for (ic = lo * 53; ic <= hic; ic += 53)
sum ^= ic;
return sum;
}
|
This transformation is allowed by the C standard, but it is invalid for
wraparound arithmetic when INT_MAX / 53 < hi, because then the
overflow in computing expressions like hi * 53 can cause the
expression i <= hi to yield a different value from the
transformed expression ic <= hic.
For this reason, compilers that use loop induction and similar
techniques often do not support reliable wraparound arithmetic when a
loop induction variable like ic is involved. Since loop
induction variables are generated by the compiler, and are not visible
in the source code, it is not always trivial to say whether the problem
affects your code.
Hardly any code actually depends on wraparound arithmetic in cases like these, so in practice these loop induction optimizations are almost always useful. However, edge cases in this area can cause problems. For example:
int j; for (j = 1; 0 < j; j *= 2) test (j); |
Here, the loop attempts to iterate through all powers of 2 that
int can represent, but the C standard allows a compiler to
optimize away the comparison and generate an infinite loop,
under the argument that behavior is undefined on overflow. As of this
writing this optimization is not done by any production version of
GCC with `-O2', but it might be performed by other
compilers, or by more aggressive GCC optimization options,
and the GCC developers have not decided whether it will
continue to work with GCC and `-O2'.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Ideally the safest approach is to avoid signed integer overflow entirely. For example, instead of multiplying two signed integers, you can convert them to unsigned integers, multiply the unsigned values, then test whether the result is in signed range.
Rewriting code in this way will be inconvenient, though, particularly if
the signed values might be negative. Also, it may hurt
performance. Using unsigned arithmetic to check for overflow is
particularly painful to do portably and efficiently when dealing with an
integer type like uid_t whose width and signedness vary from
platform to platform.
Furthermore, many C applications pervasively assume wraparound behavior and typically it is not easy to find and remove all these assumptions. Hence it is often useful to maintain nonstandard code that assumes wraparound on overflow, instead of rewriting the code. The rest of this section attempts to give practical advice for this situation.
If your code wants to detect signed integer overflow in sum = a +
b, it is generally safe to use an expression like (sum < a) != (b
< 0).
If your code uses a signed loop index, make sure that the index cannot overflow, along with all signed expressions derived from the index. Here is a contrived example of problematic code with two instances of overflow.
for (i = INT_MAX - 10; i <= INT_MAX; i++)
if (i + 1 < 0)
{
report_overflow ();
break;
}
|
Because of the two overflows, a compiler might optimize away or transform the two comparisons in a way that is incompatible with the wraparound assumption.
If your code uses an expression like (i * 2000) / 1000 and you
actually want the multiplication to wrap around on overflow, use
unsigned arithmetic
to do it, e.g., ((int) (i * 2000u)) / 1000.
If your code assumes wraparound behavior and you want to insulate it against any GCC optimizations that would fail to support that behavior, you should use GCC's `-fwrapv' option, which causes signed overflow to wrap around reliably (except for division and remainder, as discussed in the next section).
If you need to port to platforms where signed integer overflow does not reliably wrap around (e.g., due to hardware overflow checking, or to highly aggressive optimizations), you should consider debugging with GCC's `-ftrapv' option, which causes signed overflow to raise an exception.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Overflow in signed
integer division is not always harmless: for example, on CPUs of the
i386 family, dividing INT_MIN by -1 yields a SIGFPE signal
which by default terminates the program. Worse, taking the remainder
of these two values typically yields the same signal on these CPUs,
even though the C standard requires INT_MIN % -1 to yield zero
because the expression does not overflow.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In C99, preprocessor arithmetic, used for #if expressions, must
be evaluated as if all signed values are of type intmax_t and all
unsigned values of type uintmax_t. Many compilers are buggy in
this area, though. For example, as of 2007, Sun C mishandles #if
LLONG_MIN < 0 on a platform with 32-bit long int and 64-bit
long long int. Also, some older preprocessors mishandle
constants ending in LL. To work around these problems, you can
compute the value of expressions like LONG_MAX < LLONG_MAX at
configure-time rather than at #if-time.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Most modern hosts reliably fail when you attempt to dereference a null pointer.
On almost all modern hosts, null pointers use an all-bits-zero internal
representation, so you can reliably use memset with 0 to set all
the pointers in an array to null values.
If p is a null pointer to an object type, the C expression
p + 0 always evaluates to p on modern hosts, even though
the standard says that it has undefined behavior.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Buffer overruns and subscript errors are the most common dangerous errors in C programs. They result in undefined behavior because storing outside an array typically modifies storage that is used by some other object, and most modern systems lack runtime checks to catch these errors. Programs should not rely on buffer overruns being caught.
There is one exception to the usual rule that a portable program cannot
address outside an array. In C, it is valid to compute the address just
past an object, e.g., &a[N] where a has N elements,
so long as you do not dereference the resulting pointer. But it is not
valid to compute the address just before an object, e.g., &a[-1];
nor is it valid to compute two past the end, e.g., &a[N+1]. On
most platforms &a[-1] < &a[0] && &a[N] < &a[N+1], but this is not
reliable in general, and it is usually easy enough to avoid the
potential portability problem, e.g., by allocating an extra unused array
element at the start or end.
Valgrind can catch many overruns. GCC users might also consider using the `-fmudflap' option to catch overruns.
Buffer overruns are usually caused by off-by-one errors, but there are more subtle ways to get them.
Using int values to index into an array or compute array sizes
causes problems on typical 64-bit hosts where an array index might
be 2^31 or larger. Index values of type size_t avoid this
problem, but cannot be negative. Index values of type ptrdiff_t
are signed, and are wide enough in practice.
If you add or multiply two numbers to calculate an array size, e.g.,
malloc (x * sizeof y + z), havoc ensues if the addition or
multiplication overflows.
Many implementations of the alloca function silently misbehave
and can generate buffer overflows if given sizes that are too large.
The size limits are implementation dependent, but are at least 4000
bytes on all platforms that we know about.
The standard functions asctime, asctime_r, ctime,
ctime_r, and gets are prone to buffer overflows, and
portable code should not use them unless the inputs are known to be
within certain limits. The time-related functions can overflow their
buffers if given timestamps out of range (e.g., a year less than -999
or greater than 9999). Time-related buffer overflows cannot happen with
recent-enough versions of the GNU C library, but are possible
with other
implementations. The gets function is the worst, since it almost
invariably overflows its buffer when presented with an input line larger
than the buffer.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The keyword volatile is often misunderstood in portable code.
Its use inhibits some memory-access optimizations, but programmers often
wish that it had a different meaning than it actually does.
volatile was designed for code that accesses special objects like
memory-mapped device registers whose contents spontaneously change.
Such code is inherently low-level, and it is difficult to specify
portably what volatile means in these cases. The C standard
says, "What constitutes an access to an object that has
volatile-qualified type is implementation-defined," so in theory each
implementation is supposed to fill in the gap by documenting what
volatile means for that implementation. In practice, though,
this documentation is usually absent or incomplete.
One area of confusion is the distinction between objects defined with volatile types, and volatile lvalues. From the C standard's point of view, an object defined with a volatile type has externally visible behavior. You can think of such objects as having little oscilloscope probes attached to them, so that the user can observe some properties of accesses to them, just as the user can observe data written to output files. However, the standard does not make it clear whether users can observe accesses by volatile lvalues to ordinary objects. For example:
/* Declare and access a volatile object. Accesses to X are "visible" to users. */ static int volatile x; x = 1; /* Access two ordinary objects via a volatile lvalue. It's not clear whether accesses to *P are "visible". */ int y; int *z = malloc (sizeof (int)); int volatile *p; p = &y; *p = 1; p = z; *p = 1; |
Programmers often wish that volatile meant "Perform the memory
access here and now, without merging several memory accesses, without
changing the memory word size, and without reordering." But the C
standard does not require this. For objects defined with a volatile
type, accesses must be done before the next sequence point; but
otherwise merging, reordering, and word-size change is allowed. Worse,
it is not clear from the standard whether volatile lvalues provide more
guarantees in general than nonvolatile lvalues, if the underlying
objects are ordinary.
Even when accessing objects defined with a volatile type,
the C standard allows only
extremely limited signal handlers: the behavior is undefined if a signal
handler reads any nonlocal object, or writes to any nonlocal object
whose type is not sig_atomic_t volatile, or calls any standard
library function other than abort, signal, and (if C99)
_Exit. Hence C compilers need not worry about a signal handler
disturbing ordinary computation, unless the computation accesses a
sig_atomic_t volatile lvalue that is not a local variable.
(There is an obscure exception for accesses via a pointer to a volatile
character, since it may point into part of a sig_atomic_t
volatile object.) Posix
adds to the list of library functions callable from a portable signal
handler, but otherwise is like the C standard in this area.
Some C implementations allow memory-access optimizations within each
translation unit, such that actual behavior agrees with the behavior
required by the standard only when calling a function in some other
translation unit, and a signal handler acts like it was called from a
different translation unit. The C standard hints that in these
implementations, objects referred to by signal handlers "would require
explicit specification of volatile storage, as well as other
implementation-defined restrictions." But unfortunately even for this
special case these other restrictions are often not documented well.
See (gcc)Volatiles section `When is a Volatile Object Accessed?' in Using the GNU Compiler Collection (GCC), for some
restrictions imposed by GCC. See (libc)Defining Handlers section `Defining Signal Handlers' in The GNU C Library, for some
restrictions imposed by the GNU C library. Restrictions
differ on other platforms.
If possible, it is best to use a signal handler that fits within the limits imposed by the C and Posix standards.
If this is not practical, you can try the following rules of thumb. A
signal handler should access only volatile lvalues, preferably lvalues
that refer to objects defined with a volatile type, and should not
assume that the accessed objects have an internally consistent state
if they are larger than a machine word. Furthermore, installers
should employ compilers and compiler options that are commonly used
for building operating system kernels, because kernels often need more
from volatile than the C Standard requires, and installers who
compile an application in a similar environment can sometimes benefit
from the extra constraints imposed by kernels on compilers.
Admittedly we are handwaving somewhat here, as there are few
guarantees in this area; the rules of thumb may help to fix some bugs
but there is a good chance that they will not fix them all.
For volatile, C++ has the same problems that C does.
Multithreaded applications have even more problems with volatile,
but they are beyond the scope of this section.
The bottom line is that using volatile typically hurts
performance but should not hurt correctness. In some cases its use
does help correctness, but these cases are often so poorly understood
that all too often adding volatile to a data structure merely
alleviates some symptoms of a bug while not fixing the bug in general.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Almost all modern systems use IEEE-754 floating point, and it is safe to assume IEEE-754 in most portable code these days. For more information, please see David Goldberg's classic paper What Every Computer Scientist Should Know About Floating-Point Arithmetic.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A C or C++ program can exit with status N by returning
N from the main function. Portable programs are supposed
to exit either with status 0 or EXIT_SUCCESS to succeed, or with
status EXIT_FAILURE to fail, but in practice it is portable to
fail by exiting with status 1, and test programs that assume Posix can
fail by exiting with status values from 1 through 255. Programs on
SunOS 2.0 (1985) through 3.5.2 (1988) incorrectly exited with zero
status when main returned nonzero, but ancient systems like these
are no longer of practical concern.
A program can also exit with status N by passing N to the
exit function, and a program can fail by calling the abort
function. If a program is specialized to just some platforms, it can fail
by calling functions specific to those platforms, e.g., _exit
(Posix) and _Exit (C99). However, like other functions, an exit
function should be declared, typically by including a header. For
example, if a C program calls exit, it should include `stdlib.h'
either directly or via the default includes (see section Default Includes).
A program can fail due to undefined behavior such as dereferencing a null pointer, but this is not recommended as undefined behavior allows an implementation to do whatever it pleases and this includes exiting successfully.
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated on January, 20 2010 using texi2html 1.76.