Chapter 17: Function-like macros
The chapter introduction explains why inline functions are usually safer than function-like macros, while identifying when macros remain useful.
After this chapter you will be able to
- Inline functions over macros. Explain why an inline function is preferable to a function-like macro when the number and types of arguments are known, using the squaring example.
- Early textual macro replacement. Explain that macro replacement happens during preprocessing, before C tokens are interpreted as keywords, types, variables, or other later constructs.
- Function-like macro expansion sequence. Trace how a function-like macro invocation is recognized, how its arguments are separated and expanded, and how parameter substitution and further macro replacement produce the inserted text. Explain why the macro definition is temporarily disabled during this process.
- Macro retention and function names. Explain when a function-like macro name is not expanded and how that permits a function with the same name to be defined or used as a function pointer.
- Function interfaces for argument checking. Explain why a function is preferable to a function-like macro when C types adequately describe a fixed set of arguments, and how a small static inline wrapper can enable checks with little expected overhead.
- String literal macro enforcement. Explain why a function interface cannot express all the desired constraints on a string argument, and how the macro’s empty-string-literal construction restricts an argument to a string literal.
- Trace format checking. Explain why the tracing macro restricts its format argument to a string literal, and how its later version keeps argument checking active even when debugging output is disabled.
- Compound literal compatibility checks. Explain why casting a value to `void*` weakens the pointer-printing macro’s checks, and how initializing a `void*` compound literal checks assignment compatibility instead.
- Invocation context in traces. Explain why a debugging macro can report information about where it was invoked, and identify the context supplied by __LINE__, __FILE__, __DATE__, __TIME__, and __func__.
- Stringification and nested expansion. Describe what # does to a macro argument and trace why converting __LINE__ to its numeric text requires an extra macro-expansion step.
- Variable-length function arguments. Explain that a function such as printf can accept a known initial argument followed by an arbitrary number of additional arguments, and that its declaration marks this with ... at the end of the parameter list. State that this feature is not type safe and is therefore dangerous.
- Variadic macro arguments. Explain how a variadic macro declares and accesses its variable-length argument list, and describe how that list can be forwarded or stringified.
- Conditional variadic expansion. Explain how `__VA_OPT__` handles an empty variable argument list, both when inserting a separator and when choosing between target macros.
- Trace values construction. Describe how the tracing example combines a macro and an inline function to print a variable list of values without supplying a format specifier for each value.
- Variadic argument promotions. Explain how arguments passed to a variadic parameter are converted, including the special treatment of float and the promotion of char and short.
- Argument type conventions. Explain why a variadic function needs valid information about each argument's type and why callers may need to force arguments to specific types for portability.
- Variadic list length convention. Explain why a variadic function must establish its own convention for determining how many arguments to read.
- Optional argument macro dispatch. Describe how the strtod wrapper selects a macro variant according to whether its optional argument is supplied.
- Cascading DEFAULT macro expansions. Trace the section's general pattern for supplying one, two, or three defaults through DEFAULT macros.
- Conversion call wrapper macros. Explain how CALL macros make the default-argument expansions useful for conversion-function calls.
What it builds on
- Inline functions (chapter 16) · Assumes familiarity with inline functions.
- Macros (chapter 10) · Introduced function-like macros and their potential to obscure code.
- Macros (chapter 5) · Introduces macros as named replacements.
- Function pointers (chapter 11) · Provides context for using a pointer to a function.
- Read-only objects (chapter 5) · The distinction noted here between string literals being read-only and being const-qualified.
- Compound literals (chapter 5) · Compound literals as a way to create an object of a specified type.
- Specifying values (chapter 5) · The value-dependent types of decimal integer literals.
- Formatted output (chapter 8) · The requirement that formatted-output arguments match their format specifiers.
Section by section
Introduction
The chapter introduction explains why inline functions are usually safer than function-like macros, while identifying when macros remain useful.
- Inline functions over macros: Explain why an inline function is preferable to a function-like macro when the number and types of arguments are known, using the squaring example.
Why it matters: Choosing an inline function can avoid unexpected repeated side effects without giving up the performance sought from a macro. - Appropriate role for macros: Distinguish the general advice to avoid replaceable function-like macros from the situations in which a macro can offer something a function cannot, and describe the intended simplicity of its interface.
Why it matters: This helps you judge whether a macro earns its added complexity and whether callers can use it simply.
Key terms: function-like macro, inline function, type-safe interface, side effects, function-like macros, replacement string, simple interface
17.1 How function-like macros work
This section explains how function-like macros are expanded by the preprocessor and when a macro name is retained so it can refer to a function.
- Early textual macro replacement: Explain that macro replacement happens during preprocessing, before C tokens are interpreted as keywords, types, variables, or other later constructs.
Why it matters: This explains why a macro invocation is transformed before the compiler interprets the resulting C code. - Function-like macro expansion sequence: Trace how a function-like macro invocation is recognized, how its arguments are separated and expanded, and how parameter substitution and further macro replacement produce the inserted text. Explain why the macro definition is temporarily disabled during this process.
Why it matters: The sequence lets you reason about the text an invocation will produce without treating it as a function call. - Macro retention and function names: Explain when a function-like macro name is not expanded and how that permits a function with the same name to be defined or used as a function pointer.
Why it matters: Macro retention makes it possible to refer to a real function even when a function-like macro has the same name.
Key terms: textual replacement, preprocessing, early translation phase, function-like macro, argument list, parameters, replacement text, macro invocation, macro retention, function decay
17.2 Argument checking
This section shows how to use functions and function-like macros to catch argument errors that ordinary C function interfaces cannot always express.
- Function interfaces for argument checking: Explain why a function is preferable to a function-like macro when C types adequately describe a fixed set of arguments, and how a small static inline wrapper can enable checks with little expected overhead.
Why it matters: A function interface can catch mistakes without giving up the expected efficiency of a small inline wrapper. - String literal macro enforcement: Explain why a function interface cannot express all the desired constraints on a string argument, and how the macro’s empty-string-literal construction restricts an argument to a string literal.
Why it matters: Restricting an argument to a literal can enforce a property that the function’s parameter type cannot state. - Trace format checking: Explain why the tracing macro restricts its format argument to a string literal, and how its later version keeps argument checking active even when debugging output is disabled.
Why it matters: This catches format mismatches before a debug build unexpectedly exposes them. - Arithmetic argument type tricks: Describe how adding `+0` or `+0.0F` to a macro argument constrains or converts its type for the section’s debugging-print example.
Why it matters: Simple expressions in a macro expansion can impose useful type constraints without a function parameter. - Compound literal compatibility checks: Explain why casting a value to `void*` weakens the pointer-printing macro’s checks, and how initializing a `void*` compound literal checks assignment compatibility instead.
Why it matters: The macro can check that a value is suitable for pointer printing instead of hiding an error behind a cast.
Key terms: function-like macros, static, inline, argument checking, string literal, string_literal, function interface, fprintf, format string, TRACE_PRINT1
17.3 Accessing the context of invocation
This section shows how macros can capture information from their invocation site for debugging traces, including how to stringify a line number safely.
- Invocation context in traces: Explain why a debugging macro can report information about where it was invoked, and identify the context supplied by __LINE__, __FILE__, __DATE__, __TIME__, and __func__.
Why it matters: Invocation-site information makes a debugging message easier to trace back to its source. - Line number formatting risk: Explain why printing __LINE__ with a format that assumes int can be unsafe, and describe the two remedies given here.
Why it matters: A trace macro should not silently assume that every source line number has the same integer type. - Stringification and nested expansion: Describe what # does to a macro argument and trace why converting __LINE__ to its numeric text requires an extra macro-expansion step.
Why it matters: The extra expansion step lets a trace include the actual line number as text rather than the literal name __LINE__.
Key terms: context of invocation, __LINE__, __FILE__, __DATE__, __TIME__, __func__, int, unsigned long, fprintf, stringification
17.4 Variable-length argument lists
Introduces variable-length argument lists in functions and explains why their lack of type safety is a concern.
- Variable-length function arguments: Explain that a function such as printf can accept a known initial argument followed by an arbitrary number of additional arguments, and that its declaration marks this with ... at the end of the parameter list. State that this feature is not type safe and is therefore dangerous.
Why it matters: This explains both how calls such as printf accept varying numbers of arguments and why that facility calls for caution.
Key terms: variable-length argument lists, ..., type safe
17.4.1 Variadic macros
This section explains how variadic macros capture, forward, stringify, conditionally expand, and count variable argument lists, then uses those techniques in tracing macros.
- Variadic macro arguments: Explain how a variadic macro declares and accesses its variable-length argument list, and describe how that list can be forwarded or stringified.
Why it matters: These are the basic tools the tracing examples use to pass along or display a flexible list of expressions. - Conditional variadic expansion: Explain how `__VA_OPT__` handles an empty variable argument list, both when inserting a separator and when choosing between target macros.
Why it matters: It lets one macro form valid calls whether optional arguments are present or absent. - Trace values construction: Describe how the tracing example combines a macro and an inline function to print a variable list of values without supplying a format specifier for each value.
Why it matters: The example shows how a macro can prepare argument text and an array while a function handles the printing. - Argument counting trick: Explain how `ALEN` derives the length of a nonempty macro argument list and why its stated use avoids the empty-list case.
Why it matters: The count supplies the array length used by the trace-values example.
Key terms: variadic macros, ..., __VA_ARGS__, stringified, __VA_OPT__, FPRINTF_II, FPRINTF_IIIplus, TRACE_VALUES, ALEN, compound literal
17.4.2 A detour: Variadic functions
This section explains how variadic function arguments are converted and accessed, and why their types and list length require explicit conventions.
- Variadic argument promotions: Explain how arguments passed to a variadic parameter are converted, including the special treatment of float and the promotion of char and short.
Why it matters: The type received by a variadic function may differ from the type written in the call. - Argument type conventions: Explain why a variadic function needs valid information about each argument's type and why callers may need to force arguments to specific types for portability.
Why it matters: A call that appears to work on one platform can fail when the function expects a different argument type. - Variadic list access: Describe how a variadic function uses <stdarg.h> to access its arguments and how formatted-printing work can instead be delegated to vfprintf.
Why it matters: Passing a va_list to an existing printing function avoids writing the argument-by-argument type handling yourself. - Variadic list length convention: Explain why a variadic function must establish its own convention for determining how many arguments to read.
Why it matters: Without an agreed way to determine the length, the function cannot know when to stop reading arguments.
Key terms: variadic parameter, float, double, char, short, variadic function, format argument, NULL, portability, <stdarg.h>
17.5 Default arguments
The section shows how function-like macros can supply commonly omitted arguments to C library conversion functions.
- Reasons for default arguments: Explain why wrappers for conversion functions can supply commonly used arguments.
Why it matters: This explains why the extra macro layer is useful rather than just how it is constructed. - Optional argument macro dispatch: Describe how the strtod wrapper selects a macro variant according to whether its optional argument is supplied.
Why it matters: Optional-argument dispatch is the first step toward supplying a default only when the caller leaves an argument out. - Cascading DEFAULT macro expansions: Trace the section's general pattern for supplying one, two, or three defaults through DEFAULT macros.
Why it matters: This cascading pattern extends the same defaulting idea to calls with several optional arguments. - Conversion call wrapper macros: Explain how CALL macros make the default-argument expansions useful for conversion-function calls.
Why it matters: It connects the default-selection macros to the function calls they are meant to simplify.
Key terms: default arguments, strtoul, strtod, endptr, base, __VA_OPT__, __VA_ARGS__, NPTR, _I, _II
17.6 Summary
Where this is used later
- Macros (chapter 18) · How function-like macros substitute tokens
- The auto feature (chapter 18) · How function-like macros use their arguments.
- Generic selection (chapter 18) · The generic wrapper described there uses this checked function.
- Variadic functions (chapter 18) · Provides the argument-conversion background for variadic functions.
- Default promotions and conversions (chapter 18) · The earlier discussion of variadic functions.
Concepts explained in this chapter
- function-like macroA function-like macro is a C preprocessor macro invoked with parentheses and arguments that expands its invocation into replacement source text.
- va_listva_list is the C library type used to represent the state of traversal through a variable argument list.
- va_startva_start is a C standard-library macro that initializes a variable argument list for access by va_arg and related operations.
- macro expansionMacro expansion is the recursive process of replacing a macro invocation and any expandable macros in its arguments with source tokens.
- stringification operatorMacro argument stringification is the C preprocessing technique of converting a macro argument's textual spelling into a string literal.
- __VA_ARGS____VA_ARGS__ is a predefined C preprocessor identifier that expands to the arguments supplied for a variadic macro's ellipsis parameter.
- __VA_OPT____VA_OPT__ is a C23 preprocessor feature that conditionally includes replacement tokens when a variadic macro receives one or more variable arguments.
- default valueA default argument is a value supplied automatically when a caller omits an otherwise optional function argument.
- variadic macroA variadic macro is a function-like C preprocessor macro that accepts a variable number of arguments through an ellipsis parameter.
- replacement textReplacement text is the source token sequence specified by a macro definition for insertion when the macro expands.
- inline functioninline is a C function specifier that permits a definition to suggest inline substitution of a function's body at call sites without requiring the compiler to perform that substitution.
- string_literalstring_literal is a book-specific function-like macro that accepts a string literal by requiring adjacent empty string literals in its replacement.
- compound literalA compound literal is C syntax consisting of a type name in parentheses followed by an initializer that creates an unnamed object with the specified type and value.
- format stringA format string is a character sequence containing literal text and format specifiers that controls formatted output conversion.
- variadic argumentsA variadic parameter is the ellipsis-based parameter portion of a C function declaration that accepts additional arguments.
- stdarg.hstdarg.h is the C standard header that declares the types and macros for processing variable argument lists.
- va_argva_arg is a C library macro that retrieves the next argument from a va_list using a specified type.
- vfprintfvfprintf is a C standard-library function that formats output to a stream using a va_list of arguments.
- macro invocationA macro invocation is source text consisting of a function-like macro name followed by parentheses containing its arguments.
- preprocessorThe C preprocessor is a translation phase that processes preprocessing directives and expands macros before compilation.
- strtoulstrtoul is a C standard-library function that converts the initial part of a string to an unsigned long value using an end-pointer and integer-base parameter.
- token concatenationThe token concatenation operator ## joins two preprocessing tokens into a single preprocessing token during macro replacement.
- temporary objectA temporary object is an unnamed C object created for an intermediate expression or initialization and given automatic temporary lifetime.
- macroA macro definition is a preprocessing directive that associates a macro name and optional parameters with replacement text.
- __FILE____FILE__ is a predefined C macro that expands to a string literal naming the current source file.
- __TIME____TIME__ is a predefined C macro that expands to a string literal containing the time of translation.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
