_Pragma和宏替换

11
在实现我的自己的C11编译器时,我正在尝试弄清楚如何正确处理_Pragma关键字/操作符。C11 §6.10.9将_Pragma描述为一个操作符,因此似乎可以用宏重新定义它,即#define _Pragma(x) SOME_OTHER_MACRO(x)。此外,语句#undef _Pragma应该没有效果(假设没有先前的#define _Pragma)。这类似于可以#define关键字的方式,例如旧的VC++ hack#define for if (0);else for。然而,由于_Pragma运算符在第3个翻译阶段期间进行评估,与执行预处理器指令相同,不清楚是否属于异常情况;标准未提到使用_Pragma作为宏名称是否是未定义的行为。
我使用以下代码对GCC进行了一些测试:
#define PRAGMA _Pragma
PRAGMA("message \"hi\"")

_Pragma ("message \"sup\"")

#undef PRAGMA

#undef _Pragma
//#define _Pragma(x)
_Pragma("message \"hello\"")

使用 gcc -std=c11 -pedantic -Wall -Wextra -c 编译会输出:

tmp.c:2:1: note: #pragma message: hi
 PRAGMA("message \"hi\"")
 ^
tmp.c:4:1: note: #pragma message: sup
 _Pragma ("message \"sup\"")
 ^
tmp.c:8:8: warning: undefining "_Pragma" [enabled by default]
 #undef _Pragma
        ^
tmp.c:10:9: error: expected declaration specifiers or ‘...’ before string constant
 _Pragma("message \"hello\"")
         ^

如果我添加行#undef _Alignof,GCC不会对此抱怨。

这表明GCC通过宏实现了_Pragma(通过警告消息),而取消定义它会导致编译错误。如果取消注释#define _Pragma(x),则错误会消失(因为字符串文字消失)。

所以,我的问题是:

  1. 实现是否允许将_Pragma定义为仅为宏,而不将其作为运算符实现?
  2. 如果不允许,则GCC这样做是否有误?
  3. 如果_Pragma应该是运算符,那么将_Pragma定义为宏是否属于未定义行为?
  4. _Pragma的评估和其他预处理指令之间是否存在排序?或者它们具有相同的“优先级”(即按顺序评估它们)?

再次查看C11标准,并没有提到_Pragma除了它是用于#pragma指令的运算符。


2
预计在回答这个问题时会有相当程度的观点发挥作用(例如void main(void)讨论),但是你的研究很明显,你的问题也提出得很好。这应该会产生有趣的回复。(+1) - ryyker
我不确定我是否正确理解了标准,但我认为C11 7.1.3 p3适用:“如果程序删除(通过#undef)上述第一组中任何标识符的宏定义,则其行为未定义。” 其中提到的“第一组”是以下划线后跟另一个下划线或大写字母开头的标识符。 - mafso
@mafso 看起来只有在第一次出现宏定义的情况下才适用,所以像 #ifndef _Pragma <newline>#undef _Pragma<newline>#endif 这样的代码不会引发 UB。 - Drew McGowen
如果C11标准将_Pragma定义为形式为_Pragma(string-literal)的一元运算符表达式,而某个实现选择以不同的方式实现它,那么该实现是否被认为是C?(参考C11标准中的6.10.9 Pragma运算符,委员会草案-2011年4月12日) - ryyker
类似于关键字可以被 #define 定义,比如旧的 VC++ hack #define for if (0) ; else for — …哇。我以前从未听说过这个 — https://dev59.com/tnNA5IYBdhLWcg3wYMt- - Rusty Shackleford
1个回答

7
不需要特殊规则来禁止_Pragma成为宏名称。由于带有前置下划线和大写字母,它属于保留标识符,无论如何都不应该使用。使用保留标识符会导致程序未定义的行为,编译器可能会做任何事情。
实现可以将其实现为宏,但只要您正确使用它,那么这对您来说应该是透明的,也就是说,只要您不胡乱搞。唯一重要的是,实现必须保证对_Pragma的参数进行“去字符串化”和“标记化”的处理,就好像在第3阶段中进行一样(如果仅仅是宏,则难以做到),并且处理结果#pragma指示在第4阶段中被处理。

“保留供任何用途”这个标准的含义几乎不清楚 - 当然,像 int ln = __LINE__; 这样的语句可能被认为是对标识符 __LINE__ 的使用(除非“保留供任何用途”不包括强制性标识符)。 - Drew McGowen
@DrewMcGowen: "保留供任何用途"指用户不能为任何用途声明或定义该形式的标识符(因为它是保留的)。相比之下,仅为文件作用域标识符保留或仅在包含特定头文件时保留的标识符形式则不同。 - Michael Burr
2
@DrewMcGowen,这有一个非常精确的意义,在7.1.3中提供。第2段说了你不应该怎么做:如果程序在保留字上下文中声明或定义标识符(除非根据7.1.4允许),或者将保留标识符定义为宏名称,则行为是未定义的。 - Jens Gustedt
以一个下划线和大写字母开头,它是保留标识符之一,您不应该使用... - ryyker
“_Pragma”是C运算符中的“完整成员”吗?我的意思是,int *c = _Pragma(ObscureIntAllocator);是否合法的C代码? - cesss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接