为什么 C 语言允许用户创建名称与预先存在的库函数相同的宏?

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 这样的库函数来命名宏?

Eri*_*hil 5

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 标准以这种方式设计至少有三个原因(好的或坏的):

  • 它允许灵活性和扩展性。C 被设计成一种可移植的语言,并不是说程序在任何地方都以相同的方式工作,而是说 C 可以相对容易地为各种计算平台实现。C 标准具有灵活性,因此该语言可以根据平台进行调整,并且它允许实现提供超出标准规定的语言扩展。
  • 它减少了编译器的工作负载。通过不对编译器强加很多要求,编写它们变得更容易。(那么,是否对程序进行额外检查就变成了质量问题,而不是遵守 C 标准。这个政策运作良好。即使没有标准的要求,今天广泛使用的编译器的质量比几十年前要高得多.)
  • 它允许在 C 标准发布之前编写的一些代码继续使用该标准。在有关于使用库函数作为宏名称的规则之前,可能已经编写了一些这样做的程序(程序员可能这样做是因为他们想要稍微改变库函数,至少在外观上)。要求编译器拒绝这些程序将需要完成一些工作以使这些程序能够使用新的语言标准。