使用 _Generic 关键字的宏的 Eclipse CDT 语法错误

Eclipse CDT syntax error for macro using _Generic keyword

我正在使用内置 CDT 9.3.0 的 Oxygen。

当我使用我定义的使用 _Generic 的宏时,所有这些宏的使用都带有 "syntax error" 下划线,但项目编译正常(设置为使用我的 makefile)。

在阅读了一个 similar so 问题后,由于 _Generic 从 C11 开始可能不受 eclipse 代码分析的支持,我尝试为我的宏定义定义一个符号为空,但它没有用。 (在项目设置中,C/C++ General->Paths and Symbols->Symbols Tab, GNU C, added symbol CONVERT(...) without a value and added a symbol CONVERT(X), and CONVERT()和 CONVERT 没有值)。

例如我的宏是:

#define FIRST_(_1, ...) _1
#define FIRST(...) FIRST_(__VA_ARGS__, _1, _2, _3)

#define CONVERT(...)                            \
                _Generic((FIRST(__VA_ARGS__)),  \
                    char*       : toText,   \
                    int         : toInt,    \
                    ) (__VA_ARGS__)

和用法点,给出了语法错误:

void* test = CONVERT("testme");

正如 @ErikW 指出的那样,_Generic 是 Eclipse CDT 的解析器尚不支持的 C11 功能。 This bug tracks 添加了对它的支持。

(顺便说一句,非常欢迎contributions对Eclipse CDT的C11支持!)


可以使用宏解决此问题。

尝试在 "Paths and Symbols" 中定义另一个版本的 CONVERT(...) 宏的问题在于,在那里定义的宏被视为您将它们写在文件的最顶部。实际代码中的后续重新定义会覆盖 "Paths and Symbols".

中的定义

我可以想到两种方法来解决这个问题:


方法一

CDT 定义了一个特殊的宏 __CDT_PARSER__,它在解析代码时计算结果为真,但在实际编译代码时计算结果为假。

您可以利用它为 CDT 的目的定义不同版本的 CONVERT(...)

#ifdef __CDT_PARSER__
    #define CONVERT(...)
#else
    #define CONVERT(...)                            \
                    _Generic((FIRST(__VA_ARGS__)),  \
                        char*       : toText,   \
                        int         : toInt,    \
                        ) (__VA_ARGS__)
#endif

这几乎可行,但不完全可行。我们仍然得到语法错误,因为这一行:

void* test = CONVERT("testme", 42);

现在将扩展为:

void* test = ;

如您所见,我们实际上并不想要 CONVERT(...) 的空扩展。我们想要一个将解析为变量初始值设定项的扩展。 0 有效:

#ifdef __CDT_PARSER__
    #define CONVERT(...) 0
#else
    ...
#endif

方法二

我们可以将 _Generic(...) 本身定义为用于 CDT 目的的宏,而不是定义 CONVERT(...) 的不同版本。

这一次,我们可以在"Paths and Symbols"中进行,因为代码中没有重新定义_Generic(...)会搞砸

所以让我们在"Paths and Symbols"中定义一个符号,名称为_Generic(...),值为空。

现在,这一行:

void* test = CONVERT("testme", 42);

将扩展为:

void* test = _Generic((FIRST("testme", 42)),  \
                    char*       : toText,   \
                    int         : toInt,    \
                    ) ("testme", 42)

这将依次扩展为:

void* test = ("testme", 42);

它解析(("testme", 42) 解析为带括号的逗号表达式,因此是一个有效的初始值设定项)。

这种方法的优点是您不需要修改实际代码,并且它处理 _Generic 宏的所有使用,而不仅仅是 CONVERT 中的那个。

另一方面,对于 _Generic 宏的某些其他用途,此特定扩展可能无法解析。如果是这种情况,您可能会想出一个不同的扩展来解析所有用途,否则您可以使用方法 1。