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

getc() as macro and C standard library function definition, coherent?

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

Any function declared in a header may be additionally implemented as a function-like macro defined in the header...

Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once...

然后对于getc,[7.21.7.5的getc函数]:

The getc function is equivalent to fgetc, except that if it is implemented as a macro, it may evaluate stream more than once, so the argument should never be an expression with side effects.

是否定义getc:

的确,getc 宏可能会多次计算其 fp 参数。如果 §7.1.4 说 "Unless otherwise noted, any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once."

可能会更好

getc 多次评估其 fp 参数的实现可以追溯到 stdio 的黎明。所以这并不奇怪,并且基本上没有依赖于单一评估或在多重评估下会中断的代码。 (谁写过像 getc(*fpp++) 这样的东西?是的,我可以想出一个例子,甚至不是 100% 人为的例子,但实际上,这种例子非常罕见。)

真正非常关心的代码总是可以调用 fgetc。这就是它的用途。

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

标准说……

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

Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: …

Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown below can be used to ensure the declaration is not affected by such a macro.

Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro.185) The use of #undef to remove any macro definition will also ensure that an actual function is referred to.

Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once, fully protected by parentheses where necessary, so it is generally safe to use arbitrary expressions as arguments.186) Likewise, those function-like macros described in the following subclauses may be invoked in an expression anywhere a function with a compatible return type could be called.187)

185) This means that an implementation shall provide an actual function for each library function, even if it also provides a macro for that function.

186) Such macros might not contain the sequence points that the corresponding function calls do.

187) Because external identifiers and some macro names beginning with an underscore are reserved, implementations may provide special semantics for such names. For example, the identifier _BUILTIN_abs could be used to indicate generation of in-line code for the abs function. Thus, the appropriate header could specify

#define abs(x) _BUILTIN_abs(x)

for a compiler whose code generator will accept it. In this manner, a user desiring to guarantee that a given library function such as abs will be a genuine function may write

#undef abs

whether the implementation’s header provides a macro implementation of abs or a built-in implementation. The prototype for the function, which precedes and is hidden by any macro definition, is thereby revealed also.

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

您还从 §7.21.7.5 的 getc 的定义中引用了 material:

The getc function is equivalent to fgetc, except that if it is implemented as a macro, it may evaluate stream more than once, so the argument should never be an expression with side effects.

(其中 stream 是用于 getc 的参数的名称。)

解释标准

你问(略有转述):

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

    没有。 §7.1.4开头说了'unless explicitly stated otherwise',然后给出了一系列的一般规则,然后getc的规范明确指出了其他的。

  • 反之亦然吗?

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

  • 这是否与标准不一致?

    我看不出这里有任何不连贯之处。

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

    1. getc 可能无法单独实现为宏(脚注 185)。还必须有一个实现相同功能的实际函数。实现可以很简单:

      int (getc)(FILE *fp) { return getc(fp); }
      
    2. 实现 getc 的宏被明确允许多次计算其参数(但不是必须这样做)。 §7.21.7.5 中的规范明确表示可以,而 §7.1.4 中的规范明确表示允许 §7.21.7.5 更改通常禁止此类行为的 §7.1.4 的一般规则。