Enum Width Across MCU Modules: Detect ABI Mismatches Before a Library Upgrade

Matching enum names and numeric values do not prove that an application and precompiled library agree on binary layout. Compare the effective enum representation and every affected structure before passing objects across the boundary. If one component uses short enums and another does not, reject the integration until the ABI contract is reconciled.

This guide pins C11 and GCC 14.2.0. The executed qualification fixture used separate translation units on x86-64 Linux. Its sizes are host observations, not universal MCU defaults. C++ fixed-underlying-type enums, Linux shared-library naming and general SDK packaging are outside scope.

Find the enum-bearing boundary

Start from exported functions, callback signatures and shared structures. List enum parameters, enum return values, pointers to enums, enum arrays and structures containing enum members. Include structures passed indirectly through a generic pointer: removing the type from a function declaration does not remove the callee’s interpretation of the pointed-to storage.

The GCC 14.2.0 code-generation manual explicitly identifies binary incompatibility between code generated with and without -fshort-enums. The option chooses a sufficiently small integer representation for the declared enum range. Therefore a source-compatible-looking header can still describe incompatible binary objects when compiled under different options.

Application and library independently report enum size, record size and member offsets through an already agreed scalar probe interface; a mismatch blocks all enum-bearing calls before objects cross the boundary.
Compare both compiled sides before using the disputed layout. A successful link is not the layout check.

Generate evidence independently on both sides

For each public enum-bearing structure, collect enum size and alignment, structure size and alignment, member offsets and array stride. Record the enumerator range and build flags that produced those values. An application-only sizeof check proves the application’s view, not the library’s view.

Obtain corresponding evidence from the actual library build: a provider-generated manifest, a diagnostic function compiled into that library, or suitable retained debug/build artifacts. Do not compile a second copy of the header with application flags and label its result “library ABI.” That simply repeats the same assumption twice.

A diagnostic function must itself use an already agreed interface. For example, the host fixture exported a function that filled four size_t entries while both components used the same target and size_t ABI. A production diagnostic might use an explicitly versioned byte format instead. Avoid returning the disputed enum or structure from the very function intended to detect its incompatibility.

Use a controlled mismatch without invoking it

The original fixture used this C interface shape:

enum mode { IDLE = 0, ACTIVE = 1, EXTRA = 2 };
struct packet {
    uint8_t tag;
    enum mode state;
    uint16_t count;
};

It built the application and library separately, queried the library’s layout, and compared four quantities before allowing any enum-bearing call. No mismatched structure was dereferenced across the boundary. The following are executed GCC 14.2.0 host results:

Build of the component Enum size Packet size State/count offsets
Ordinary options 4 12 4 / 8
fshort-enums 1 4 1 / 2
fshort-enums, EXTRA changed to 300 2 6 2 / 4

Matched ordinary builds and matched short-enum builds passed the layout gate. Both mixed-flag combinations were rejected. A short-enum library built with the expanded enumerator range was also rejected by the older short-enum application. All five cases linked successfully; the explicit comparison supplied the missing evidence.

The third row is especially useful during upgrades. Even consistent short-enum flags do not guarantee a stable width when a new enumerator expands the required range. Treat enum-range changes as ABI review events rather than assuming that adding a constant is always binary-compatible.

Interpret a mismatch before changing flags

Check the compiler invocation for each object or archive member that contributes to the interface. Search build-system defaults, per-file options, vendor toolchain settings and generated compile commands. Also inspect packing attributes and pragmas on the containing structure. An enum-width mismatch and a structure-packing mismatch can coexist.

The versioned type-attribute manual documents packed enum behavior and its relationship to short enums. The WG14 N1570 draft, section 6.7.2.2, makes a C enum’s compatible integer type implementation-defined within its representability requirement. Neither source supports assuming that every C enum occupies an int on every target.

Do not repair the application with a local cast while leaving the library layout unchanged. A cast at a function call cannot relocate fields in a shared structure. Changing only the top-level link command also cannot retroactively rebuild incompatible objects already stored in an archive.

A hypothetical library replacement gate

Suppose a hypothetical motor-monitor library adds a diagnostic state while retaining the same function names. The application receives a new archive and header. Its release gate first compares the library’s build manifest and layout probe with the application build, before enabling callbacks or sharing packet arrays.

If the gate finds different state offsets, the integration remains disabled. The team can obtain a library built for the existing ABI, rebuild both sides under one approved contract, or introduce a conversion wrapper whose external representation is explicitly stable. The choice belongs to the interface owner; silently guessing the library’s representation is not an acceptable fallback.

Close the qualification beyond sizeof

  1. Compare exact headers, enumerator ranges, compiler identity, CPU ABI and effective enum/packing options.
  2. Check independently generated sizes, alignments, offsets and array stride.
  3. Verify parameter and return calling conventions for the actual target ABI.
  4. Exercise every supported enum value and reject unknown values according to the interface contract.
  5. Repeat with production optimization and the exact distributed archive.

Equal layout probes are necessary evidence for this interface, not a complete proof of calling conventions, ownership or semantic compatibility. Preserve the probe and manifest with each release so the next library upgrade can be accepted or rejected before incompatible objects reach running firmware.

Similar Posts