【问题标题】:getc() as macro and C standard library function definition, coherent?getc() 作为宏和 C 标准库函数定义,一致吗?
【发布时间】:2017-02-09 04:07:01
【问题描述】:

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

在头文件中声明的任何函数都可以另外实现为 头文件中定义的类函数宏...

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

那么对于getc[7.21.7.5的getc函数]

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

getc的定义是否:

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

【问题讨论】:

  • @Olaf 不明白你的意思... getc(*f++) 是否安全?好的,可能不安全,但与 7.1.4 相矛盾。我错了吗?
  • 嗯,我可能在阅读不存在的标准(“流”)时添加了一个“the”(刚刚检查了标准,stream是参数的名称)。再想一想,它可能确实意味着指针本身。我想说 7.1.4 仅适用于不需要是宏但可能是函数的名称。这样可以保证相同的行为,而无需关心它是宏还是函数。它不适用于有保证的宏。只是不要有争论的副作用,应该不是太大的问题。
  • 当然这不是一个真正的问题,因为很少有边框效果作为参数。无论如何阅读std,我知道getc是一个函数(“getc函数”),所以7.1.4适用,“另外实现为类似函数”+“如果它被实现为宏”导致我与“评估”不一致一次”和“可能评估不止一次”。 std 提示确保函数调用:(getc)(arg).
  • 顾名思义,它是一个“类函数宏”。但请随时向 C 工作组提交缺陷报告。那么,可能已经有一个了。你检查了吗?
  • 没有缺陷报告;任何对此提交缺陷的尝试都将立即被拒绝。

标签: c function macros language-lawyer libc


【解决方案1】:

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

标准说……

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 单独实现为一个宏(这似乎不合规但是……),该宏可能会对其参数进行两次评估?

    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 的一般规则。

【讨论】:

  • 完美!甚至没有注意到段落的第一句话,重点是(错误的)不一致。
【解决方案2】:

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

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

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

【讨论】:

  • 当然这很少见(实际上从来没有写过这样的东西,但毕竟以“并行”方式读取的一组读者可能很有用)。但我正在阅读标准,发现这个(显然)矛盾。
  • @Jean-BaptisteYunès 明白了。我很惊讶标准没有“除非另有说明”条款,因为getcputc 宏的情况很重要,而且经常被误解。
  • 该标准在 7.1.4 中包含“除非另有明确说明”,然后在 7.21.7.5 中明确说明。 “这可能很好”评论可以升级为“注意 §7.1.4 声明……”。
  • @JonathanLeffler 谢谢,这让我感觉好多了。
猜你喜欢
  • 2018-11-07
  • 2011-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-03
  • 2021-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多