Preprocessor Macros V.S. Inline Functions
General introduction and comparison between macors and inline functions.
Macros
Macros rely on textual substitution. The preprocessor macros are just substitution patterns in code before the compilation, so there is no type-checking at that time. However, the compiler still does type checking after macros had been expanded.
That is, the text defined in macros will be substituted by preprocessor. For example,
#include <stdio.h>
#define SQUARE(x) x*x
int main() {
int s = SQUARE(5); // This will be converted to: int s = 5*5;
printf("%d\n", s);
return 0;
}
By #define SQUARE(x) x*x
,
the pattern of SQUARE(x)
in the code will be replaced by x*x
.
Therefore, the int s = SQUARE(5);
will be converted to int s = 5*5
.
int main() {
printf("%d\n", SQUARE(3+2));
printf("%lf\n", (double)1/SQUARE(5));
return 0;
}
The result for #define SQUARE(x) x*x
is:
11
1.000000
obviously, #define SQUARE(x) x*x
will cause error
when we apply SQUARE(3+2)
and 1/SQUARE(5)
.
We will get 3+2*3+2
(11) and 1/5*5
(1.00) rather than 25 and 0.04.
Try #define SQUARE(x) (x)*(x)
:
25
1.000000
The (double)1/SQUARE(5)
will be converted to (double)1/(5)*(5)
(1),
that is still wrong.
The answer is #define SQUARE(x) ((x)*(x))
:
25
0.040000
It seems that we solve the problem. However, it does NOT. The result of
int a = 5;
printf("%d\n", SQUARE(a++)); // expected output is 25
printf("%d\n", a); // expected output is 6
is
30
7
The substitution of SQUARE(a++)
is ((a++)*(a++))
.
So, they will be5*6
, and then a = 7
after this statement.
The first a = a + 1
run immediately after 5*(...)
.
In the same manner, the second a = a + 1
run immediately after 5*6
.
It's execution order may be similar to
a = 5;
int result;
result = a;
a = a + 1;
result = result * a;
a = a + 1;
return result;
The following is one solution for our problem using GCC.
#define SQUARE(x) ({ \
typeof (x) _x = (x); \
_x * _x; \
})
The typeof
is a non-standard GNU extension to declare a variable
having the same type as another.
Thus, typeof(x) _x = (x);
will be turned into int _x = (x++);
.
See more typeof here and
discussion about #define Square(x) here.
When to use macros
There are common cases:
- To alleviate the function-call overhead at the potential cost of larger code size.
- Define a general function beyond types, such as:
#define SUM(array, size) ({ \ typeof(*array) total = 0; \ int i = 0; \ for (i = 0 ; i < size ; i++) { \ total += array[i]; \ } \ total; \ })
Side effect
If the macros doesn't be defined well, then the behavior would be out of your expectation without any compiler warnings or errors.
#include <stdio.h>
#define MAX(a,b) ((a < b) ? b : a)
int main() {
int a = 5, b = 10;
printf("%d\n", MAX(a++, b++)); // expected 10, but output will be 11
return 0;
}
Safer macros
Please read safer macros; It shows some example to check type in macros.
Inline functions
Inline functions are actual functions whose copy of the function body are injected directly into each place the function is called. The insertion (called inline expansion or inlining) occurs only if the compiler's cost/benefit analysis show it to be profitable. Same as the macros, inline expansion eliminate the overhead associated with function calls.
Inline functions are parsed by the compiler, whereas macros are expanded by the preprocessor.
See more detail here.
When should we use inline functions instead of macros
The preprocessor macros are just substitution patterns in code before the compilation, so there is no type-checking at that time. We might unconsciously use the wrong type for the macro without any debugging hints from the compiler.
Considering the following code:
#include <stdio.h>
#include <stdbool.h> // for type bool
#define TURN_UP(audio) ++audio.vol
struct Power {
bool ac; // true: Alternating current (AC), false: direct current(DC)
int vol; // Voltage
int amp; // Ampere
int freq; // Frequency for AC. This must be zero for DC.
};
struct Audio {
int freq; // Frequency
int vol; // Volume
int dur; // Duration
};
int main() {
// C99 style for initializing struct data
struct Power u = { .ac = 0, .vol = 5, .amp = 1, .freq = 0 }; // USB
printf("Power: %s %dV %dA %dHz\n", (u.ac) ? "AC" : "DC", u.vol, u.amp, u.freq);
struct Audio a = { .freq = 440, .vol = 10, .dur = 5 }; // Note for A4
printf("Sound: %dDB %dHz for %d Seconds \n", a.vol, a.freq, a.dur);
// Turn up the volume of the sound
TURN_UP(u); // Oops! Typo! It should be TURN_UP(a);
printf("Sound: %dDB %dHz for %d Seconds \n", a.vol, a.freq, a.dur);
return 0;
}
If we unconsciously use TURN_UP(u)
instead of TURN_UP(a)
,
then GCC won't give us hints to make it right.
Thus, we expect:
Power: DC 5V 1A 0Hz
Sound: 10DB 440Hz for 5 Seconds
Sound: 11DB 440Hz for 5 Seconds
but we will get:
Power: DC 5V 1A 0Hz
Sound: 10DB 440Hz for 5 Seconds
Sound: 10DB 440Hz for 5 Seconds
This unexpected result will be a serious problem when our code base has hundreds of thousands line. We will have no idea what's going wrong without any clue. It's better to have hints to find out the cause.
#include <stdio.h>
#include <stdbool.h> // for type bool
struct Power {
bool ac; // true: Alternating current (AC), false: direct current(DC)
int vol; // Voltage
int amp; // Ampere
int freq; // Frequency for AC. This must be zero for DC.
};
struct Audio {
int freq; // Frequency
int vol; // Volume
int dur; // Duration
};
void turn_up(struct Audio*) __attribute__((always_inline));
void inline turn_up(struct Audio* a) {
++a->vol;
}
int main() {
// C99 style for initializing struct data
struct Power u = { .ac = 0, .vol = 5, .amp = 1, .freq = 0 }; // USB
printf("Power: %s %dV %dA %dHz\n", (u.ac) ? "AC" : "DC", u.vol, u.amp, u.freq);
struct Audio a = { .freq = 440, .vol = 10, .dur = 5 }; // Note for A4
printf("Sound: %dDB %dHz for %d Seconds \n", a.vol, a.freq, a.dur);
// Turn up the volume of the sound
turn_up(&u); // Oops! Typo! It should be turn_up(&a);
printf("Sound: %dDB %dHz for %d Seconds \n", a.vol, a.freq, a.dur);
return 0;
}
On the contrary, if we use inline
functions to do it,
gcc will give a hint for this problem.
$ gcc test.c
test.c:34:11: warning: incompatible pointer types passing 'struct Power *' to parameter of type 'struct Audio *' [-Wincompatible-pointer-types]
turn_up(&u); // Oops! Typo! It should be turn_up(&a);
^~
test.c:19:35: note: passing argument to parameter 'a' here
void inline turn_up(struct Audio* a) {
^
1 warning generated.
However, it may not be helpful enough. When we're developing a large software, we will usually have a very long compiler logs. It's very hard to trace.
In this case, giving error is better than warning.
Luckily, we can define some warning as error by ourselves.
It's a good options for debugging.
We can use -Werror
flags to make all warnings into errors,
or use -Werror=
to make the specified warning into an error.
In our case, we can define incompatible-pointer-types
as an error.
That is,
$ gcc -Werror=incompatible-pointer-types [FILENAME]
The result will be:
test.c:34:11: error: incompatible pointer types passing 'struct Power *' to parameter of type 'struct Audio *'
[-Werror,-Wincompatible-pointer-types]
turn_up(&u); // Oops! Typo! It should be turn_up(&a);
^~
test.c:19:35: note: passing argument to parameter 'a' here
void inline turn_up(struct Audio* a) {
^
1 error generated.
Therefore, we can avoid this problem upon finishing compilation. See more detail here
Another case to replace macro by inline
function is
how static
variable is used. We give an example as follows:
#include <stdio.h>
#define COUNT ({ \
static int a = 0; \
printf("%d\n", ++a); \
})
void count() __attribute__((always_inline));
void inline count() {
static int a = 0;
printf("%d\n", ++a);
}
int main() {
printf("--- macro ---\n");
COUNT;
COUNT;
COUNT;
printf("--- inline ---\n");
count();
count();
count();
return 0;
}
output:
--- macro ---
1
1
1
--- inline ---
1
2
3
Comparison
- Macros are not type safe itself.
- Macros may result unexpected result if it's not defined well.
- You cannot step through a
#define
in the debugger, but you can step through an inline function. - Macros are more flexible because they don't do type-checking and they can embed other macros.
- Inline functions are not always guaranteed to be inlined.
Some compiler needs to do extra configuration, or works only in release build.
- Recursive functions is an example where most compilers ignore to inline
- If you use
-Os
to make GCC/G++ optimize for minimum size, then inline functions may be ignored. - GCC/G++ will try to inline most possible functions
if
-O2
or-O3
is used for maximum speed.
- Inline functions can provide scope for variables.
- While preprocessor macros can use code blocks
{...}
to achieve this. - However, the static variables will not behave as your expectation in macros.
- While preprocessor macros can use code blocks
- Macros can't access
private
orprotected
variables, while inline functions are possible. - In some case, it's not possible to be inlined.
See more discussion here