这个宏会导致 GCC 崩溃吗?读完你就会有答案
本文的目标是向您介绍 C 语言宏的壮丽世界。
在 C 中,以 # 开头的行由编译器在编译源文件时解释。这些称为预处理器指令。宏就是其中之一。
小历史点:
C 语言宏是随第一个 C 语言标准引入的,称为 ANSI C(或 C89),
于 1989 年由美国国家标准协会 (ANSI) 标准化。然而,在此标准化之前,宏已经成为 20 世纪 70 年代使用的经典 C(或 K&R C)语言的一部分
。 最初的 C 编译器由 Dennis Ritchie 为 UNIX 操作系统开发,已经通过预处理器包含了基本形式的宏,允许使用 #define 进行定义。
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
定义的工作方式非常容易理解:编译器将代码中出现的所有内容替换为定义的值。它使用以下语法 #define
有点像“Ctrl-f 并替换”。
我们可以使用定义来定义可以在代码中使用的函数。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
我们可以有条件地声明宏。
如果名称已经定义,那么我们执行以下代码。
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
在本例中,我使用了 #ifndef,但它也存在:
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
我们喜欢用批量评论来表示 #if 的结束。这是一个可以让您更好地浏览代码的约定。
您可以在前面的示例中看到我使用了关键字 __FUNCTION__ 和 __LINE__。
正如您可以想象的,这些是编译器将替换为正确值的宏。
有一个常见预定义宏的列表。
请注意,有所谓的系统特定宏。
小型非详尽列表:
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
在这里,我们可以看到我们生成了可变参数宏,在创建日志时特别有用。
(即使使用 printfs 生成日志不是一个好主意。)
为此,我们必须创建一个外部文件,通常命名为 *.def,尽管没有约定。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
这种宏非常有用。我必须承认,它很少在源代码中找到,但它允许您修改程序的操作,而无需修改源代码。有趣的是,它经常用于创建内核。它允许您生成全局结构,例如 IDT 和 GDT。
注意:首先快速澄清一下,宏是很棒的工具,但你必须小心。你绝对不应该使用这种类型的宏:
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
举个例子:MIN(2 5, fibo(25))
MIN(2 5, fibo(25)) => (2 5
这里的问题是计算优先级。编译器将首先执行比较,然后执行加法,因此为 2 (1)。我们通过使用宏参数添加括号来纠正此问题。
// Ici, l'opérateur ## est l'opérateur de concaténation #define DEBUG_PRNTF(fmt, ...) printf("LOG" ## fmt, __VA_ARGS__);
由于您永远不知道用户将传递什么作为参数,因此请始终在参数上加上括号。
MIN(2 5, fibo(25)) => (2 5
我们注意到编译器做了一个愚蠢而令人讨厌的替换,这意味着我们将计算 fibo(25) 两次。我让你想象一下它是否是一个递归实现。
为了解决这个问题,我们在 if 之前声明一个中间变量。
// color.def X(NC, "\e[0m", "No Color", 0x000000) X(BLACK, "\e[0;30m", "Black", 0x000000) X(GRAY, "\e[1;30m", "Gray", 0x808080) X(RED, "\e[0;31m", "Red", 0xFF0000) X(LIGHT_RED, "\e[1;31m", "Light Red", 0xFF8080) X(GREEN, "\e[0;32m", "Green", 0x00FF00) X(LIGHT_GREEN, "\e[1;32m", "Light Green", 0x80FF80) X(BROWN, "\e[0;33m", "Brown", 0xA52A2A) X(YELLOW, "\e[1;33m", "Yellow", 0xFFFF00) X(BLUE, "\e[0;34m", "Blue", 0x0000FF) X(LIGHT_BLUE, "\e[1;34m", "Light Blue", 0xADD8E6) X(PURPLE, "\e[0;35m", "Purple", 0x800080) X(LIGHT_PURPLE, "\e[1;35m", "Light Purple", 0xEE82EE) X(CYAN, "\e[0;36m", "Cyan", 0x00FFFF) X(LIGHT_CYAN, "\e[1;36m", "Light Cyan", 0xE0FFFF) X(LIGHT_GRAY, "\e[0;37m", "Light Gray", 0xD3D3D3) X(WHITE, "\e[1;37m", "White", 0xFFFFFF)
在这里,这纯粹是为了好玩而编写的多余代码。我不一定建议您在代码中使用这些宏。
我只是在享受乐趣(人生中的一件好事)。
typedef struct { const char *name; const char *ansi_code; const char *description; unsigned int rgb; } Color; #define X(NAME, ANSI, DESC, RGB) { #NAME, ANSI, DESC, RGB }, Color colors[] = { #include "color.def" }; #undef X #define X(NAME, ANSI, DESC, RGB) printf("%s (%s) = %s\n", #NAME, DESC, #RGB); void print_colors() { // Bien entendu, on pourrait itérer sur la structure créée mais c'est une illustration #include "color.def" } #undef X
我会让你用一点 -fsanitize=address 进行测试。真的很疯狂。我们甚至可以看到 auto_free 函数的改进,它将结构名称的字符串作为参数来进行切换。
更多的 chill 函数,我们只计算函数的执行时间。对于基准测试非常有用。
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
小 X 宏,它将宏作为参数并对其进行扩展。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
在这里,我们实际上用宏生成整个函数,因为 C 没有限制。我也是?
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
现在是时候总结一下了。我们看到了很多很酷的东西。如果您有兴趣,您可以自由地自己探索宏。还有很多东西值得一看。
所以,结论:RTFM。
PS: 至于标题,宏不是递归的,它们仅以深度 1 扩展,在我们目前的情况下,GCC 将对 INC 进行隐式声明并崩溃。
以上是#define INC(a) INC(a ?的详细内容。更多信息请关注PHP中文网其他相关文章!