There are several things that are either unspecified or undefined in the various C standards, that can impact portability. The ones you may already know about are:
In this section we shall discuss a few more that arise in practice and can cause headaches.
Shifts by negative numbers and by numbers greater than or equal to the bit-width of the underlying type are undefined. Also, the left of a negative signed integer is also unspecified.In practice, the biggest problems arise when, on a 32-bit processor, you by 32. On some processors, the answer is 0. On other processors, this becomes a no-op. In general, on most processors,
x>>yis usually implemented as
31(e.g. x86) or
Another potential problem that could come up is that left shifts of signed numbers could pad with zeros instead of the sign bit. Alternatively, it could be defined so that a left- by
N was exactly the same as division by
assert( -3>>1 == -2 ); /* arithmetic right */ assert( -3>>1 == 2147483646); /* 32 bit, zero padding right */ assert( -3>>1 == -1); /* right as division */
If we are searching an array using the library routine
bsearch() and two elements in the array are equal to the key, then it is unspecified which of the two elements is returned.
More problematically, if we are sorting an array using
qsort() and two elements in the are array are equal, then it is unspecified how the two elements are ordered. They will be adjacent to each other, but it is unspecified which will be first.
These routines can and do differ from implementation to implementation. Consequently, a program will exhibit different behavior on different systems, if it uses
qsort() to sort arrays with some equal elements.
Partly because of this, and partly because
qsort() ends up being inefficient (in terms of performance), I no longer use
Instead, we have written a "template-like" sorting module, along the lines described earlier. This has several benefits:
void *. This makes for safety.
Occasionally, we need to impose a total ordering on objects. This is useful, for instance, if we want to have a set of objects that efficiently supports the union operation. If we sort all objects in sets in terms of a total ordering, when merging two sets we need to only traverse the elements of each set once.
A possible way of imposing a total ordering on objects is to use the values of the pointers; i.e. object A precedes/succeeds object B exactly if the address of A is less than/greater than the address of B.
Don't do this. The relative order of objects is now dependent on the memory allocator. If you move to a system with a different allocator, the order, and therefore, the behavior of the program will change. Worse, a change in the memory allocation/deallocation in one part of the program can change the relative order of objects, and therefore program behavior, in some other part of the program. This can lead to some non-trivial debugging.
There is one caveat to this. If all objects being are elements of the same array (i.e. they were allocated all at once), then the relative ordering will stay constant. In that case its all right to compare pointers. I would still recommend against it if there is a possibility that the implementation could be changed so that objects were later allocated independently.