【问题标题】:Can macros mimic void returning functions by casting to void?宏可以通过强制转换为 void 来模拟 void 返回函数吗?
【发布时间】:2015-08-08 07:07:20
【问题描述】:

所以...我有宏,它具有“SET”功能并且应该模仿一个函数,它会返回 void。我可以使用 do {...} while(0) 构造来实现此行为,但它是否合法,如果是这样,是否实现定义的行为将某些东西强制转换为 void 来实现此行为? (根据不同版本的 C 标准的话,对我来说最重要的是:C89 仍然)

更具体: 以下函数/宏的行为是否相似(包括合法并在用作右值时引发编译时错误)?

  void setFunction(int * const pVar, const int intvalue) /* as function */ 
  {
        *pVar = intvalue;
        return;
  }

  #define SET_MACRO_DW(pVar,intvalue) \
          do { \
               *(pVar) = (intvalue); \
             } while (0)

  #define SET_MACRO_C2V(pVar,intvalue) \
          ((void) (*(pVar) = (intvalue)))


  void test()
  {
       int target;
       int value = 17;
       int retDummy;

       setFunction(&target, value); /* ok */
       SET_MACRO_DW(&target, value); /* ok */
       SET_MACRO_C2V(&target, value); /* ok? */

       retDummy = setFunction(&target, value); /* CT error/warning */
       retDummy = SET_MACRO_DW(&target, value); /* CT syntax error */
       retDummy = SET_MACRO_C2V(&target, value); /* ??? */
       return;

  }

【问题讨论】:

  • 为什么不能测试呢?或者你为什么不在每个示例中扩展宏,看看它是否有意义?
  • 你为什么使用do {} while(0)这样的构造。这意味着什么!
  • @SergioFormiggini 这个结构是 AFAIK 通用的,用于将多个指令封装在一个宏中,但在末尾使用分号保留调用上下文。 “do”将允许在 IF 子句中执行(多条指令)宏,例如“if(...) macro(x,y);”在我的上下文中,它将阻止宏可以合法地用作左值。
  • @Lundin 对于任何常见情况,您都是完全正确的。 do while(0) 是大括号的冗余,通常使用它。我问这个问题找到更好的解决方案。但是根据昨天 Armali 的回答,有一种情况是这种结构更可取。
  • @MarkA。不是真的,这个答案并不是那么准确。你应该去看看赋值运算符。在 6.5.16.1 中列出了允许分配的各种情况。它们都不允许正确的操作数是 void 类型,就这么简单。因此,在所有这些情况下,您都会遇到同样多的编译器错误。

标签: c macros void c89


【解决方案1】:

以下函数/宏的行为是否相似(包括 用作右值时是否合法并引发编译时错误)?

它们在正确使用时表现相似(即不在赋值中)并且可能但不一定在用作右值时表现相似(见下文)。

它们当然只有在正确使用时才是合法的。

关于编译时错误,从 C89 到 C11 的标准在环境/概念模型/翻译环境/诊断部分与 C89 中的措辞非常相似(C99 中添加了粗体文本):

符合要求的实现应至少产生一个诊断 每个消息(以实现定义的方式标识) 包含违反任何语法规则的翻译单元或 约束,即使行为也是显式的 指定为未定义或实现定义。诊断消息不需要在其他 情况。

现在,当使用你的函数/宏作为右值时,我们有两种情况。

  1. SET_MACRO_DW()
    do while 语句在赋值表达式中显然违反了语法规则assignment-expression,因此必须生成诊断消息。

  2. setFunction()SET_MACRO_C2V()
    这些是空表达式。作为右值,它们不违反任何语法规则,所以问题是是否违反了任何约束。 语言/转换/其他操作数/void部分中的以下措辞没有从 C89 更改为 C11。

    void 表达式的(不存在的)值(具有 type void) 不得以任何方式使用,无论是隐式还是显式 转换(void 除外)不适用于此类 表达。

    此外,标准的措辞与

    在本标准中,“应”被解释为对实现或程序的要求;相反,“shall not”将被解释为禁止。

    如果违反了出现在约束之外的“应”或“不应”要求,则行为未定义。

    现在,我无法确定上述对void表达式的要求是否构成约束,所以我问What are the Constraints in Standard C? 结论是,上述内容不是标准意义上的约束,因此 不能保证将错误消息用作右值(尽管每个理智的编译器都应生成错误消息)并且使用会导致未定义的行为。但是在语言/表达式/赋值运算符/简单赋值部分中仍然存在约束

    应满足以下条件之一:……

    以下都不成立,因此我们违反了约束,并且即使行为未明确定义也会保证出现错误消息。

所以,SET_MACRO_DW() 是您介绍的三个结构中最简单、最简单的结构。

【讨论】:

  • Lundin 指出,至少在赋值中使用它是违反赋值运算符的约束
  • 很好发现 - 我会相应地更正答案。
  • 我担心您可能不得不更详尽地更正您的答案:由于可以保证会引发诊断,因此行为不再是 UB。同一问题上的 UB 和约束违规是互斥的(如果我解释 UB 权利的定义)。在这种情况下:由于我们无法在不引发诊断的情况下分配它,因此我们没有以任何方式使用 void 表达式。 :-)
  • :-) 恐怕您错过了 诊断 部分中引用的粗体文本;它表明(至少对我而言)UB 和约束违规不是相互排斥的。在这种情况下:通过违反约束,我们提出了诊断,而我们正是通过使用 void 表达式来做到这一点的。 (如果我们不使用 void 表达式,就不会违反约束。)
  • 嗯...我真的需要 C99 标准。 :-) 你说得有道理。即使我对 C89 可能是正确的(明天必须检查),您引用的段落非常清楚地表明该标准并没有限制违反约束的行为不是 UB ......直到我知道更好,我现在认为你是对的。如果这是全部事实,我更有责任分析警告和信息,因为 UB 只能存在于已编译的程序中,并且我认为违反约束会导致编译时错误(诊断 == 错误)。
猜你喜欢
  • 1970-01-01
  • 2011-01-07
  • 1970-01-01
  • 2020-06-27
  • 2011-06-17
  • 1970-01-01
  • 2011-01-15
  • 1970-01-01
相关资源
最近更新 更多