为什么C语言允许用户创建与预先存在的库函数名称相同的宏?
现在,在这个程序中没有遇到任何问题,因为我们没有在这里使用scanf函数 但是,如果我们出于某种目的使用scanf函数为什么C语言允许用户创建与预先存在的库函数名称相同的宏?,c,macros,language-lawyer,c-preprocessor,C,Macros,Language Lawyer,C Preprocessor,现在,在这个程序中没有遇到任何问题,因为我们没有在这里使用scanf函数 但是,如果我们出于某种目的使用scanf函数 %s Hello World Hello World 那么为什么C首先允许我们以库函数(如Scanf)命名宏呢?C 2018 7.1.3 1说: …以下任何子条款(包括未来的库说明)中列出的每个文件范围标识符都保留为用作宏名称,并且如果包含其任何关联头,则保留为具有相同名称空间中文件范围的标识符 scanf是一个标识符,其文件范围在下面的子条款(7.21)中列出,并且您包括它
%s Hello World Hello World
那么为什么C首先允许我们以库函数(如Scanf)命名宏呢?C 2018 7.1.3 1说:
…以下任何子条款(包括未来的库说明)中列出的每个文件范围标识符都保留为用作宏名称,并且如果包含其任何关联头,则保留为具有相同名称空间中文件范围的标识符
scanf
是一个标识符,其文件范围在下面的子条款(7.21)中列出,并且您包括它的头,因此它保留用作宏名称
7.1.3.2表示:
…如果程序在其保留的上下文中声明或定义标识符(7.1.4允许的除外),或将保留标识符定义为宏名称,则行为未定义
结合起来,这些规则表明,如果您将scanf
定义为宏名称并包含
,则C标准不会对发生的情况施加任何要求,因为它未指定为程序中的错误,也不要求编译器以错误为由拒绝您的程序。编译器也被要求不拒绝您的程序
有一种方法可以避免这个问题:您可以省略#include
,而是自己声明printf
(以及您使用的
中的任何其他函数)。C标准显式命令允许这样做,然后您可以为scanf
自由定义宏
这是大多数C标准的典型特征:它不会阻止您做可能导致问题的事情。特别是,它不需要编译器警告您。C标准的设计至少有三个原因(好或坏):
- 它允许灵活性和扩展性。C被设计成一种可移植语言,并不是说程序在任何地方都以同样的方式工作,而是说C可以相对容易地在各种计算平台上实现。C标准允许灵活性,因此语言可以根据平台进行调整,并且它允许实现为语言提供超出标准规定的扩展
- 它减少了编译器的工作量。由于没有对编译器强加太多要求,因此编写编译器更容易。(那么,是否对程序实施额外检查就成了一个质量问题,而不是遵守C标准的问题。这一政策运作良好。即使没有standadr的要求,今天广泛使用的编译器的质量也比几十年前要高很多。)
- 它允许在C标准发布之前编写一些代码来继续使用该标准。在有关于使用库函数作为宏名称的规则之前,可能已经编写了一些这样做的程序(程序员可能会这样做,因为他们希望在某种程度上改变库函数,至少在外观上是这样)。要求编译器拒绝这些程序需要做一些工作才能使这些程序使用新的语言标准
scanf
的情况下,这种异常不太可能发生,因为几乎总是包含stdio.h
。但对于向后兼容性来说,例外情况很重要。它允许定义新的头,而不呈现无效的旧程序,这些程序碰巧有新头保留的名称的私有用途。
%s Hello World Hello World
# 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);
^~~~~