宏是简单的字符串替换
宏是简单的字符串替换。 (严格地说,它们使用预处理令牌,而不是任意字符串。)
#include <stdio.h>
#define SQUARE(x) x*x
int main(void) {
printf("%d\n", SQUARE(1+2));
return 0;
}
你可能希望此代码打印 9
(3*3
),但实际上会打印 5
,因为宏将扩展为 1+2*1+2
。
你应该将参数和整个宏表达式包装在括号中以避免此问题。
#include <stdio.h>
#define SQUARE(x) ((x)*(x))
int main(void) {
printf("%d\n", SQUARE(1+2));
return 0;
}
另一个问题是宏的参数不能保证只被评估一次; 它们可能根本不被评估,或者可能被多次评估。
#include <stdio.h>
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
int main(void) {
int a = 0;
printf("%d\n", MIN(a++, 10));
printf("a = %d\n", a);
return 0;
}
在此代码中,宏将扩展为 ((a++) <= (10) ? (a++) : (10))
。由于 a++
(0
)小于 10
,a++
将被评估两次,它将使 a
的值和 MIN
返回的值与你的预期不同。
这可以通过使用函数来避免,但请注意,类型将由函数定义修复,而宏可以(太)灵活地使用类型。
#include <stdio.h>
int min(int x, int y) {
return x <= y ? x : y;
}
int main(void) {
int a = 0;
printf("%d\n", min(a++, 10));
printf("a = %d\n", a);
return 0;
}
现在双重评估的问题是固定的,但是这个 min
函数不能在不截断的情况下处理 double
数据。
宏指令可以有两种类型:
#define OBJECT_LIKE_MACRO followed by a "replacement list" of preprocessor tokens
#define FUNCTION_LIKE_MACRO(with, arguments) followed by a replacement list
区分这两种类型的宏的是在 #define
之后跟随标识符的字符:如果它是一个 lparen ,它是一个类似函数的宏; 否则,它是一个类似对象的宏。如果打算编写一个类似函数的宏,宏的名称末尾和 (
之间一定不能有空格。请查看此详细说明。
Version >= C99
在 C99 或更高版本中,你可以使用 static inline int min(int x, int y) { … }
。
Version >= C11
在 C11 中,你可以为 min
写一个’type-generic’表达式。
#include <stdio.h>
#define min(x, y) _Generic((x), \
long double: min_ld, \
unsigned long long: min_ull, \
default: min_i \
)(x, y)
#define gen_min(suffix, type) \
static inline type min_##suffix(type x, type y) { return (x < y) ? x : y; }
gen_min(ld, long double)
gen_min(ull, unsigned long long)
gen_min(i, int)
int main(void)
{
unsigned long long ull1 = 50ULL;
unsigned long long ull2 = 37ULL;
printf("min(%llu, %llu) = %llu\n", ull1, ull2, min(ull1, ull2));
long double ld1 = 3.141592653L;
long double ld2 = 3.141592652L;
printf("min(%.10Lf, %.10Lf) = %.10Lf\n", ld1, ld2, min(ld1, ld2));
int i1 = 3141653;
int i2 = 3141652;
printf("min(%d, %d) = %d\n", i1, i2, min(i1, i2));
return 0;
}
通用表达式可以扩展为更多类型,例如 double
,float
,long long
,unsigned long
,long
,unsigned
- 以及写入的适当的 gen_min
宏调用。