2 c macros language-lawyer c-preprocessor
# include <stdio.h>
# define scanf "%s Hello World"
int main (void)
{
printf(scanf, scanf);
getchar();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码片段中,在执行代码之前,会发生宏扩展阶段。在这种情况下,每次出现 'scanf' 都被替换为"%s Hello World".
因此printf(scanf,scanf)将成为
printf(“%s Hello World” , “%s Hello World”)。
最终输出将是:
%s Hello World Hello World
Run Code Online (Sandbox Code Playgroud)
现在,这个程序没有遇到任何问题,因为我们这里没有使用 scanf 函数。
但是,如果我们出于某种目的使用 scanf 函数
# include <stdio.h>
# define scanf "%s Hello World"
int main(void)
{
int x;
printf(scanf, scanf);
scanf("%d",&x);
getchar();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我们遇到一个错误:
main.c: In function ‘main’:
main.c:2:17: error: called object is not a function or function pointer
# define scanf "%s Hello World"
^
main.c:7:4: note: in expansion of macro ‘scanf’
scanf("%d",&x);
^~~~~
Run Code Online (Sandbox Code Playgroud)
为什么 C 允许我们首先以像 Scanf 这样的库函数来命名宏?
C 2018 7.1.3 1 说:
...在以下任何子条款(包括未来的库方向)中列出的具有文件范围的每个标识符都保留用作宏名称和具有相同名称空间中的文件范围的标识符(如果包含其任何相关标题)。
scanf 是具有以下子条款 (7.21) 中列出的文件范围的标识符,并且您包含其标题, ,因此保留用作宏名称。
7.1.3 2 说:
… 如果程序在保留标识符的上下文中声明或定义标识符(7.1.4 允许的情况除外),或将保留标识符定义为宏名称,则行为未定义。
结合起来,这些规则表明,如果您定义scanf为宏名称并包含<stdio.h>,则 C 标准不会对发生的事情强加任何要求——它没有被指定为您的程序中的错误,也没有要求编译器拒绝您的程序因为不正确。编译器也不需要不拒绝你的程序。
有一种替代方法可以避免此问题:您可以省略#include <stdio.h>并自行声明printf(以及<stdio.h>您使用的任何其他函数)。C 标准显式允许这样做,然后您可以自由地为scanf.
这是大部分 C 标准的典型特征:它不会阻止您做可能导致问题的事情。特别是,它不需要编译器警告您。C 标准以这种方式设计至少有三个原因(好的或坏的):