在 [7.1.4库函数的使用] 中,我读到:
标头中声明的任何函数都可以另外实现为a 标题中定义的类似函数的宏...
和
对作为宏实现的库函数的任何调用 将扩展为仅对其每个参数进行一次评估的代码...
然后对于getc
, [7.21.7.5 getc函数] :
getc函数等效于fgetc,除非它是 实现为宏,它可能不止一次评估流,所以 论证永远不应该是一个有副作用的表达。
getc
:
getc
单独实现(似乎不符合但是?)作为宏,它可能会对其参数进行两次评估?答案 0 :(得分:4)
标准中的定义是连贯的;你试图解释它们并不完全一致。
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
,所以参数永远不应该是一个表达式副作用。
(其中stream
是用于getc
的参数的名称。)
你问(略微转述):
getc
的定义是否与库函数定义相矛盾?
没有。第7.1.4节开头说'除非另有明确说明',然后给出一系列一般规则,然后getc
的规范另有明确说明。
反过来适用吗?
没有。第7.1.4节的开头部分说明任何特定函数的规范都可以覆盖第7.1.4节中的一般性。
这是标准中的不连贯吗?
我认为这里没有不一致。
或者这是否意味着如果getc
仅作为宏实现(似乎不符合...但是......),宏可以两次评估其参数?
getc
可能无法仅作为宏实现(脚注185)。还必须有一个实现相同功能的实际功能。实施可以很简单:
int (getc)(FILE *fp) { return getc(fp); }
显式允许实现getc
的宏多次评估其参数(但不要求这样做)。 §7.21.7.5中的规范明确指出它可能,并且§7.1.4中的规范明确规定允许§7.21.7.5改变通常禁止此类行为的§7.1.4的一般规则。
答案 1 :(得分:2)
getc
宏确实可能不止一次地评估其fp
参数。如果§7.1.4说“除非另有说明,任何实现为宏的库函数的调用都应扩展为仅对其每个参数进行一次计算的代码。”
getc
参数的 fp
实现可以追溯到stdio的曙光。所以这并不奇怪,并且基本上没有代码依赖于单一评估或者在多重评估下会中断。 (谁曾写过像getc(*fpp++)
这样的东西?是的,我可以想出一个例子,甚至不是100%做作的例子,但实际上,它很少见。)
真正非常关心的代码总是可以调用fgetc
。这就是它的用途。