getc()作为宏和C标准库函数定义,连贯吗?

Jea*_*nès 6 c macros function libc language-lawyer

[7.1.4库函数的使用]中,我读到:

标题中声明的任何函数可以另外实现为标题中定义的函数式宏...

任何实现为宏的库函数的调用都应扩展为仅对其每个参数进行一次计算的代码...

那么getc,[7.21.7.5 getc函数]:

getc函数等效于fgetc,除非它是作为宏实现的,它可能会多次评估流,因此参数永远不应该是带有副作用的表达式.

定义是getc:

  • 与库函数定义相矛盾?
  • 反过来?
  • 这是标准中的不连贯吗?
  • 或者这是否意味着如果getc单独实现(似乎不符合但是?)作为宏,它可以两次评估其参数?

Jon*_*ler 5

标准中的定义是一致的;您对它们的尝试解释并不完全一致。

标准说……

ISO/IEC 9899:2011 (C11) 标准说(从 §7.1.4 引用更多的材料,并将一大段的部分分成几部分):

除非在以下详细说明中另有明确说明,否则以下每项陈述均适用:……

在头文件中声明的任何函数都可以额外实现为在头文件中定义的类函数宏,因此如果在包含其头文件时显式声明了库函数,则可以使用下面显示的技术之一来确保声明不是受这样的宏影响。

函数的任何宏定义都可以通过将函数名称括在括号中来局部抑制,因为该名称后面没有左括号,表示宏函数名称的扩展。出于相同的句法原因,即使库函数也被定义为宏,也允许获取库函数的地址。185)使用#undef删除任何宏定义也将确保引用实际函数。

对作为宏实现的库函数的任何调用都应扩展为对每个参数仅计算一次的代码,并在必要时完全受括号保护,因此使用任意表达式作为参数通常是安全的。186)同样,以下子条款中描述的那些类似函数的宏可以在表达式中调用具有兼容返回类型的函数可以调用的任何地方。187)

185)这意味着一个实现应该为每个库函数提供一个实际的函数,即使它也为那个函数提供了一个宏。

186)这样的宏可能不包含相应函数调用所做的序列点。

187)因为外部标识符和一些以下划线开头的宏名称是保留的,所以实现可能会为这些名称提供特殊的语义。例如,标识符_BUILTIN_abs可用于指示abs函数的内嵌代码的生成。因此,适当的标题可以指定

#define abs(x) _BUILTIN_abs(x)
Run Code Online (Sandbox Code Playgroud)

对于其代码生成器将接受它的编译器。以这种方式,希望保证给定库函数(例如abs将是真正的函数)的用户可以编写

#undef abs
Run Code Online (Sandbox Code Playgroud)

实现的标头是提供宏实现abs还是内置实现。函数的原型在任何宏定义之前并被任何宏定义隐藏,因此也被揭示。

请特别注意脚注 185 的内容。

您还引用了getc来自 §7.21.7.5的定义中的材料:

getc函数等效于fgetc,除了如果它作为宏实现,它可能会计算stream多次,因此参数永远不应是具有副作用的表达式。

stream用于参数的名称在哪里getc。)

解释标准

你问(稍微解释一下):

  • 的定义是否与getc库函数定义相矛盾?

    不是。第 7.1.4 节的开头说“除非另有明确说明”,然后给出了一系列通用规则,然后规范中getc明确说明了其他情况。

  • 反过来适用吗?

    不可以。§7.1.4 的开头部分说任何特定函数的规范都可以覆盖 §7.1.4 的一般性。

  • 这是标准的不连贯吗?

    我看不出这里有什么不连贯的地方。

  • 或者这是否意味着如果getc仅作为宏实现(这似乎不符合但......),宏可能会评估其参数两次?

    1. getc不能仅作为宏实现(脚注 185)。还必须有一个实现相同功能的实际功能。实现可以很简单:

      int (getc)(FILE *fp) { return getc(fp); }
      
      Run Code Online (Sandbox Code Playgroud)
    2. 宏实现getc明确允许多次评估其参数(但不需要这样做)。§7.21.7.5 中的规范明确表示可以,而 §7.1.4 中的规范明确表示允许 §7.21.7.5 更改 §7.1.4 中通常禁止此类行为的一般规则。