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

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

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

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

然后对于getc[7.21.7.5 getc函数]

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

getc的定义是:

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

标准中的定义是连贯的; 你试图解释它们并不是完全一致的。

标准说……

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

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

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

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

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

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

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

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

 #define abs(x) _BUILTIN_abs(x) 

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

 #undef abs 

实现的头部是否提供了abs或内置实现的宏实现。 由此揭示了函数的原型,其在任何宏定义之前并且被任何宏定义隐藏。

特别注意脚注185的内容。

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

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

(其中streamgetc参数的名称。)

解释标准

你问(略微转述):

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

    不是。第7.1.4节的开头说’除非另有明确说明’,然后给出一系列一般规则,然后getc的规范明确说明。

  • 反过来适用吗?

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

  • 这是标准中的不一致吗?

    我觉得这里没有不一致。

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

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

       int (getc)(FILE *fp) { return getc(fp); } 
    2. 明确允许实现getc的宏多次计算其参数(但不要求这样做)。 §7.21.7.5中的规范明确指出它可能,并且§7.1.4中的规范明确规定允许§7.21.7.5改变通常禁止此类行为的§7.1.4的一般规则。

确实, getc宏可能不止一次地评估其fp参数。 如果§7.1.4说“除非另有说明,任何作为宏实现的库函数的调用都应扩展为仅对其每个参数进行一次计算的代码。”

多次评估其fp参数的getc实现可以追溯到stdio的曙光。 所以这并不奇怪,并且基本上没有代码依赖于单一评估或在多重评估下中断。 (谁曾经getc(*fpp++)getc(*fpp++)这样的东西?是的,我可以想出一个例子,甚至不是一个100%做作的例子,但实际上,它很少见。)

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