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.

  1. Decide which C standard and platforms the program must support.
  2. Make inputs, memory sizes, and array indexes explicit and checked.
  3. 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

#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

#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.


Suggested topics

0% read