Chapter 21 of 24
21. Portability, safety, and performance
Make it correct before making it fast
Portable code behaves consistently on the platforms it is meant to support. Safe code checks assumptions such as sizes and bounds. Fast code uses resources efficiently. These goals work best in that order: correctness first, measured performance second.
- Decide which C standard and platforms the program must support.
- Make inputs, memory sizes, and array indexes explicit and checked.
- Measure a correct version to find an actual bottleneck before optimizing it.
Do not depend on accidental compiler or machine behavior. Prefer standard, readable solutions, and record any platform-specific requirement that cannot be avoided.
Write portable C
- Use standard headers and standard language features when practical.
- Do not assume
intwidth, byte order, structure layout, or character
encoding. - Use the correct type for sizes (
size_t) and pointer differences
(ptrdiff_t). - Check input, allocation, and I/O results.
- Avoid unbounded input and unchecked indexing.
- Do not read uninitialized values or use objects outside their lifetime.
- Do not assume a particular evaluation order unless the language guarantees
it. - Test on more than one compiler when portability matters.
#include <limits.h>
#include <stddef.h>
size_t safe_count_limit(void)
{
return (size_t)INT_MAX;
}
Portable code asks the implementation for its limits, uses standard types
for sizes, and handles platform differences deliberately. Exact-width types
such as uint32_t should be used only when provided and required.
Make code safer
- Establish preconditions and invariants.
- Keep buffer capacities alongside buffers.
- Keep ownership and cleanup rules explicit.
- Prefer small functions and narrow interfaces.
- Use warnings, static analysis, tests, and sanitizers.
- Handle errors visibly rather than continuing with invalid state.
#include <stdbool.h>
#include <stddef.h>
bool valid_index(size_t index, size_t length)
{
return index < length;
}
Check a bound before indexing, and keep the length available alongside the
array pointer so the check can be made.
Optimize responsibly
First make the program correct and measure it. Use a profiler to find actual
hot spots before optimizing. Prefer clear algorithms and data layouts over
clever tricks. Re-test after optimization and compare measurements on
representative inputs.
#include <stdio.h>
#include <time.h>
int main(void)
{
clock_t start = clock();
volatile unsigned long work = 0;
for (unsigned long i = 0; i < 1000000UL; ++i) {
work += i;
}
clock_t finish = clock();
if (start != (clock_t)-1 && finish != (clock_t)-1) {
double seconds = (double)(finish - start) / CLOCKS_PER_SEC;
printf("work=%lu, CPU seconds=%.3f\n", work, seconds);
}
return 0;
}
This demonstrates a basic CPU-time measurement, not a reliable benchmark:
clock resolution, optimization, system load, and workload all matter. Use a
profiler and repeated representative runs for real performance decisions.
A repeatable safety checklist
Before using a value, ask whether it was initialized and validated. Before
indexing, verify the index is within the array's length. Before dereferencing,
verify the pointer refers to a live object. Before writing text, know the
destination capacity and preserve space for the terminator. Before allocating,
check the requested-size calculation. Before releasing memory or files,
identify who owns them and whether cleanup has already happened.
Portability also means being explicit about assumptions. If a binary format
requires exact field widths or byte order, encode and decode those fields
deliberately rather than dumping a structure. If a program depends on an
operating-system interface, isolate that interface and document the supported
platforms.
#include <assert.h>
#include <stddef.h>
struct Buffer {
size_t length;
size_t capacity;
};
int main(void)
{
struct Buffer buffer = {3, 8};
assert(buffer.length <= buffer.capacity);
return 0;
}
This assertion checks an internal invariant. External inputs still need
normal validation because assertions may be compiled out.
Checkpoint: Review one of your programs using the checklist above and
write down at least one input, bounds, lifetime, or cleanup assumption.