Designing a Type‑Generic abs Macro with C11 _Generic
Use C11 _Generic to create a zero‑overhead abs macro that selects the correct library function for int, long, float, and double arguments.
09 May 2026, 02:39 UTC

Requirements
The goal is to provide a macro ABS(x) that:
- Expands to the appropriate standard library function (
abs,labs,fabs,fabsf) based on the type ofx. - Incurs no runtime overhead; the selected function is called directly.
- Works with any C11‑conforming compiler and degrades gracefully on older compilers.
- Preserves type safety: passing an unsupported type produces a clear compile‑time error.
Smallest Suitable Design
The minimal design uses a single _Generic selection inside a macro. The controlling expression is the macro argument, and each association maps a type to the corresponding function call.
#include
#include
#define ABS(x) _Generic((x), \
signed int: abs, \
signed long: labs, \
double: fabs, \
float: fabsf)(x)
The macro expands to a function designator; the trailing (x) invokes that function. Because _Generic is resolved during translation, the generated code contains a direct call to the selected function.
Trust/Data Boundaries
There are no runtime trust boundaries: the decision is made at compile time, so the generated binary behaves as if the programmer had written the specific function call themselves. No indirect jumps, function pointers, or dynamic dispatch are introduced. The only data that crosses the macro boundary is the argument value, which is passed by value to the selected function.
Operational Checks
To verify that the macro works as intended:
- Write a test program that calls
ABSwith each supported type and prints the result. - Compile with a C11‑conforming compiler, e.g.,
gcc -std=c11 -Wall -Wextra test_abs.c -o test_abs. - Run the executable and confirm the output matches the expected absolute values.
- Inspect the generated assembly (e.g.,
objdump -d test_abs) and verify that each call site contains a direct call toabs,labs,fabs, orfabsfwith no intermediate indirection.
Example test program:
#include
#include
#include
#define ABS(x) _Generic((x), \
signed int: abs, \
signed long: labs, \
double: fabs, \
float: fabsf)(x)
int main(void) {
int i = -7;
long l = -12345L;
double d = -3.14;
float f = -2.71f;
printf("int: %d\n", ABS(i));
printf("long: %ld\n", ABS(l));
printf("double: %f\n", ABS(d));
printf("float: %f\n", ABS(f));
return 0;
}
Failure Modes
- Unsupported type: If the argument type is not listed in the generic association list (e.g.,
unsigned shortor a pointer), the compiler emits an error such as “no matching generic association”. This is a compile‑time failure, preventing silent runtime mistakes. - Multiple evaluations: Because the controlling expression appears in each generic association, a macro argument with side effects (e.g.,
ABS(x++)) would be evaluated multiple times, leading to undefined behavior. The macro as written evaluates the argument once for type selection and once for the function call, but if the argument expression is used more than once inside the macro body (as in some variations), side effects could be duplicated. - Cryptic diagnostics: Error messages from the compiler about mismatched generic associations can be hard to interpret, especially in large code bases.
Conditions That Would Change the Design
Consider revising the design when:
- Support for additional types is required (e.g.,
long doubleor integer types from<stdint.h>). The generic list must be extended accordingly. - The project must compile with pre‑C11 compilers. In that case, provide a fallback macro that uses
_Pragmaor compiler‑specific extensions (e.g., GCC’s__builtin_choose_expr) or switch to an inline function with_Genericinside it, guarded by#if __STDC_VERSION__ >= 201112L. - Side‑effect safety becomes a priority. Replace the macro with a static inline function:
static inline double abs_dbl(double x) { return fabs(x); }
static inline float abs_flt(float x) { return fabsf(x); }
static inline long abs_lng(long x) { return labs(x); }
static inline int abs_int(int x) { return abs(x); }
static inline double ABS_impl(double x) { return abs_dbl(x); }
static inline float ABS_impl(float x) { return abs_flt(x); }
static inline long ABS_impl(long x) { return abs_lng(x); }
static inline int ABS_impl(int x) { return abs_int(x); }
#define ABS(x) _Generic((x), \
double: ABS_impl, \
float: ABS_impl, \
long: ABS_impl, \
int: ABS_impl)(x)
This version evaluates the argument only once (inside the inline function) while still providing zero‑overhead dispatch because the inline functions are typically inlined.
Practical Way to Check the Result
After building the test program, run it and compare the output to a known‑good implementation (e.g., using the standard library functions directly). Any discrepancy indicates a macro expansion problem. Additionally, the assembly check described earlier confirms that no extra branches or function‑pointer loads are introduced.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.