为什么C语言允许用户创建与已有库函数同名的宏?
Why does C language allow users to create Macros whose name are the same as a pre-existing library function?
# include <stdio.h>
# define scanf "%s Hello World"
int main (void)
{
printf(scanf, scanf);
getchar();
return 0;
}
在上面的代码片段中,在代码执行之前,发生了宏扩展阶段。在此,每次出现的 'scanf' 都被 "%s Hello World"
替换。
因此printf(scanf,scanf)
会变成
printf(“%s Hello World” , “%s Hello World”)
.
最终输出将是:
%s Hello World Hello World
现在这个程序没有遇到任何问题,因为我们这里没有使用scanf函数。
但是,如果我们出于某种目的使用 scanf 函数
# include <stdio.h>
# define scanf "%s Hello World"
int main(void)
{
int x;
printf(scanf, scanf);
scanf("%d",&x);
getchar();
return 0;
}
我们遇到错误:
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);
^~~~~
为什么 C 语言首先允许我们在像 Scanf 这样的库函数之后命名宏?
C 2018 7.1.3 1 说:
… Each identifier with file scope listed in any of the following subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included.
scanf
是一个标识符,其文件范围列在以下子条款 (7.21) 中,并且您包括了它的 header, ,因此它被保留用作宏名称。
7.1.3 2 说:
… If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.
结合起来,这些规则表明,如果您将 scanf
定义为宏名称并包含 <stdio.h>
,C 标准不会对发生的情况强加任何要求——它没有被指定为您的程序中的错误,也不需要编译器拒绝您的程序不正确。编译器也不需要不拒绝你的程序。
有一个替代方法可以避免这个问题:您可以省略 #include <stdio.h>
而改为自己声明 printf
(以及您使用的 <stdio.h>
中的任何其他函数)。 C 标准显式允许这样做,然后您可以自由定义 scanf
.
的宏
这是许多 C 标准的典型特征:它不会阻止您执行可能导致问题的操作。特别是,它不需要编译器警告您。 C 标准以这种方式设计至少有三个原因(好的或坏的):
- 它允许灵活性和扩展。 C 被设计成一种可移植的语言,不是指程序在任何地方都以相同的方式工作,而是指 C 可以相对轻松地在各种计算平台上实现。 C 标准允许灵活性,因此语言可以根据平台进行调整,并且它允许实现为语言提供超出标准指定范围的扩展。
- 它减少了编译器的工作量。通过不对编译器强加很多要求,可以更容易地编写它们。 (然后,是否对程序实施额外检查成为质量问题,而不是遵守 C 标准。这个政策运作良好。即使没有标准的要求,今天广泛使用的编译器的质量也比几十年前的高得多.)
- 它允许在 C 标准发布之前编写的一些代码继续使用该标准。在有关于使用库函数作为宏名称的规则之前,可能已经编写了一些这样做的程序(程序员可能会这样做,因为他们想稍微改变库函数,至少在外观上)。要求编译器拒绝这些程序需要做一些工作才能使这些程序使用新的语言标准。
# include <stdio.h>
# define scanf "%s Hello World"
int main (void)
{
printf(scanf, scanf);
getchar();
return 0;
}
在上面的代码片段中,在代码执行之前,发生了宏扩展阶段。在此,每次出现的 'scanf' 都被 "%s Hello World"
替换。
因此printf(scanf,scanf)
会变成
printf(“%s Hello World” , “%s Hello World”)
.
最终输出将是:
%s Hello World Hello World
现在这个程序没有遇到任何问题,因为我们这里没有使用scanf函数。
但是,如果我们出于某种目的使用 scanf 函数
# include <stdio.h>
# define scanf "%s Hello World"
int main(void)
{
int x;
printf(scanf, scanf);
scanf("%d",&x);
getchar();
return 0;
}
我们遇到错误:
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);
^~~~~
为什么 C 语言首先允许我们在像 Scanf 这样的库函数之后命名宏?
C 2018 7.1.3 1 说:
… Each identifier with file scope listed in any of the following subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included.
scanf
是一个标识符,其文件范围列在以下子条款 (7.21) 中,并且您包括了它的 header, ,因此它被保留用作宏名称。
7.1.3 2 说:
… If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.
结合起来,这些规则表明,如果您将 scanf
定义为宏名称并包含 <stdio.h>
,C 标准不会对发生的情况强加任何要求——它没有被指定为您的程序中的错误,也不需要编译器拒绝您的程序不正确。编译器也不需要不拒绝你的程序。
有一个替代方法可以避免这个问题:您可以省略 #include <stdio.h>
而改为自己声明 printf
(以及您使用的 <stdio.h>
中的任何其他函数)。 C 标准显式允许这样做,然后您可以自由定义 scanf
.
这是许多 C 标准的典型特征:它不会阻止您执行可能导致问题的操作。特别是,它不需要编译器警告您。 C 标准以这种方式设计至少有三个原因(好的或坏的):
- 它允许灵活性和扩展。 C 被设计成一种可移植的语言,不是指程序在任何地方都以相同的方式工作,而是指 C 可以相对轻松地在各种计算平台上实现。 C 标准允许灵活性,因此语言可以根据平台进行调整,并且它允许实现为语言提供超出标准指定范围的扩展。
- 它减少了编译器的工作量。通过不对编译器强加很多要求,可以更容易地编写它们。 (然后,是否对程序实施额外检查成为质量问题,而不是遵守 C 标准。这个政策运作良好。即使没有标准的要求,今天广泛使用的编译器的质量也比几十年前的高得多.)
- 它允许在 C 标准发布之前编写的一些代码继续使用该标准。在有关于使用库函数作为宏名称的规则之前,可能已经编写了一些这样做的程序(程序员可能会这样做,因为他们想稍微改变库函数,至少在外观上)。要求编译器拒绝这些程序需要做一些工作才能使这些程序使用新的语言标准。