gpt4 book ai didi

c - gcc无法扩展某些宏

转载 作者:太空宇宙 更新时间:2023-11-04 02:30:23 25 4
gpt4 key购买 nike

我正在开发一个使用第三方UI库的程序,其函数的格式为Vbox(void *first, ...)。这些函数用作布局函数并接受任意数量的参数。列表的结尾由检测到的第一个空值定义。这意味着我需要记住以空结束我的列表,这是我经常做不到的事情。
因此,我创建了一些辅助宏,这些宏应该扩展到用null附加我的列表。
其形式如下:

#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)

##前的 __VA_ARGS__用于在 __VA_ARGS为空时去掉前一个逗号。
如果框实际上应该初始化为空( first):在这些情况下,用户必须显式地添加空值,因为我不能去掉 Vbox(NULL)之后的 ,(因为 __VA_ARGS__hack只在逗号位于 ##之前而不是之后时才起作用),所以用户必须给出显式的空值,这将导致以下扩展: ##,这是一个有点多余,但很好。
总的来说,这很有效,但我遇到了一个奇怪的情况,我不能完全理解。
以以下文件为例:
// expand.c
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);

#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)

static void* Test()
{
return UtlHbox(
Foo,
UtlVbox(
UtlHbox(Bar)));
}

如果运行 Vbox(NULL, NULL),将得到以下输出:
# 1 "expand.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "expand.c"
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);

static void* Test()
{
return Hbox(Foo, Vbox(UtlHbox(Bar), NULL), NULL);
}

除了最里面的UtlHbox(由于某种原因它没有被扩展,因此在编译时会抛出一个错误)之外,所有东西都按预期进行了精确的扩展。(此外,在本例中,由于没有任何相关的“include”,因此未展开空值)。在VC12(Visual Studio 2013)中,一切编译得很好。
这里发生了什么事?这是 gcc -E expand.c操作的不同含义之间的冲突吗?有什么办法解决这个问题吗?
我使用的是GCC 4.6.3,但我已经尝试用GCC 7.1在 GodBolt上编译了它,得到了相同的结果。
经过一番研究,我开始认为我遇到了一个 known problem in GCC
似乎海合会正在自作自受。如果我创建第三个宏
#define UtlZbox(first, ...) Zbox(first , ##__VA_ARGS__, NULL)

用这个新宏替换上面例子中的内部UtlHbox,输出正确:
static void* Test()
{
return Hbox(Foo, Vbox(Zbox(Bar, NULL), NULL), NULL);
}

当一个变量宏在其自身的另一个实例中重复时,GCC似乎会自动跳转。
我还做了一些其他测试(修改宏以简化可视化):
#define UtlVbox(first, ...) V(first,##__VA_ARGS__)
#define UtlHbox(first, ...) H(first,##__VA_ARGS__)

int main()
{
// HHH
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
// HHV
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
// HVH
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
// VHH
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));

return 0;
}

下面是 Godbolt's output,用GCC 7.1编译(在我的机器上用4.6.3编译会得到相同的输出):
enter image description here
成功的转换用绿色箭头标记,失败用红色箭头标记。问题似乎是当一个带有可变参数的宏X被放置在X的另一个实例的可变参数中的任何位置时(即使是作为某个其他宏Y的参数(无论是否可变)。
最后一个测试块(标记为 ##)是所有先前失败的情况的重复,只将任何未能扩展的宏替换为UtlZbox。这样做在每一种情况下都会导致适当的扩展,除了一个UtlZbox放在另一个UtlZbox的变量参数中的情况。

最佳答案

这不是虫子,这是“蓝色颜料”。
在VC12(Visual Studio 2013)中,一切编译得很好。
只是说。。。Visual Studio的预处理器是非标准的。
我遇到了一个我不太明白的奇怪情况。
……在这里我可以帮忙。首先,让我们回顾一下预处理器如何工作的一般规则。
大纲
类似于函数的宏的扩展在许多步骤中发生,我们可以调用
参数识别
参数替换
架线粘贴
重新扫描和进一步更换
在参数标识期间,只需将形式参数与调用的参数匹配。对于可变参数宏,标准要求可变参数本身具有一个或多个调用的参数。
…作为gnu扩展(您正在使用),我们可以将变化部分映射为无参数。我要把这个称为空。注意,这与empty(和占位符标记)不同;特别是,如果我们#define FOO(x,...),则调用FOO(z)__VA_ARGS__设置为空;相反,FOO(z,)将其设置为空。
在参数替换期间,应用替换列表;在替换列表中,可以用调用的参数替换形式参数。在执行此操作之前,任何未进行字符串化且未参与粘贴运算符(粘贴的左侧或右侧)的调用参数都将完全展开。
紧绷和粘贴应用下一步,以任何顺序。
执行上述步骤后,在重新扫描和进一步更换步骤期间,将再次进行最终扫描。作为一个特殊规则,在扫描特定宏的过程中,不再允许扩展同一宏。这方面的标准行话是“蓝色油漆”;宏被标记(或“蓝色油漆”)用于此扩展。整个扫描完成后,宏将“未绘制”。
解释
让我们举你的第一个例子,但我要稍微改变一下:

#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
#define foomacro Foo
UtlHbox(foomacro,UtlVbox(UtlHbox(Bar)))

在这里,我只是把“C”去掉,只关注预处理器。我还将调用更改为调用宏 foomacro以突出显示某些内容。下面是UtlHbox调用的扩展方式。
我们从参数识别开始。 UtlHbox具有形式参数 first...;调用具有参数 foomacroUtlVbox(UtlHbox(Bar))。所以 firstfoomacro__VA_ARGS__UtlVbox(UtlHbox(Bar))
接下来,我们使用替换列表执行参数替换,即:
Hbox(first, ##__VA_ARGS__,空)
……因此,在扩展后,我们将first替换为foomacro,并将foomacro替换为__VA_ARGS__。后一种情况是不同的,因为在这个替换列表中,UtlVbox(UtlHbox(Bar))是粘贴运算符的参与者(即,右手边);因此,它不会被扩展。所以我们得到这个:
Hbox(Foo, ## UtlVbox(UtlHbox(Bar)))

接下来,我们执行串接和粘贴,得到:
Hbox(Foo, UtlVbox(UtlHbox(Bar)))

接下来,我们对__VA_ARGS__应用重新扫描和进一步替换。所以我们画蓝色,然后计算字符串。你可能已经看到你自己在这里遇到麻烦了,但为了完成我会继续。
在重新扫描和进一步替换过程中,我们发现UtlHbox,这是另一个宏。这将产生宏UtlHbox的第二级求值。
在第二级参数标识中,UtlVboxUtlVboxfirst为空。
在第二级参数替换中,我们查看UtlHbox(Bar)的替换列表,它是:
Vbox(first, ##__VA_ARGS__, NULL)

由于__VA_ARGS__不是stringified或pasted,因此在替换它之前,我们对调用的参数UtlVbox进行求值。但由于UtlHbox被涂成蓝色,我们无法将其识别为宏。同时,first为空。所以我们得到:
UtlHbox(Bar)
在粘贴的第二个级别中,我们将一个放置标记粘贴到逗号的右侧,该标记为空;这将触发逗号省略规则的gnu扩展,因此生成的粘贴将删除逗号,我们将得到:
Vbox(UtlHbox(Bar), NULL)

在第二级重扫描和替换中,我们将__VA_ARGS__涂成蓝色,然后再次重扫描该片。因为Vbox(UtlHbox(Bar), ## null, NULL)仍然被涂成蓝色,所以它仍然不能被识别为宏。因为没有其他东西是宏,所以扫描完成。
所以退一级,我们得到的结论是:
Hbox(Foo, Vbox(UtlHbox(Bar), NULL))

…在继续之前,对每个进行重新扫描和替换,我们将取消绘制UtlVboxUtlHbox
解决方案
有什么办法解决这个问题吗?
好吧,请注意有两个扩展级别:一个发生在参数替换期间,另一个发生在重新扫描和替换期间。前者发生在蓝色油漆应用之前,它可以无限期地重复:
#define BRACIFY(NAME_) { NAME_ }
BRACIFY(BRACIFY(BRACIFY(BRACIFY(BRACIFY(Z)))) BRACIFY(X))

…将很高兴地扩展到:
{ { { { { Z } } } } { X } }

这看起来像你想做的。但是“参数替换”求值只在参数没有串接或粘贴时发生。因此,真正让您丧命的是gnu逗号省略特性;您对它的使用涉及将paste运算符应用于UtlVbox;这将取消您在参数替换期间扩展的各种参数的资格。相反,它们只能在重新扫描和替换期间展开,在该阶段,宏被涂成蓝色。
所以解决方法就是避免省略逗号。对你来说,这其实很简单。让我们仔细看看:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)

所以你希望UtlHbox变成__VA_ARGS__,而UtlVbox(a)变成Vbox(a, NULL)。那就这么做怎么样?
#define UtlVbox(...) Vbox(__VA_ARGS__, NULL)
#define UtlHbox(...) Hbox(__VA_ARGS__, NULL)

现在这个:
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));

…扩展到:
Hbox(Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Vbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Vbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Vbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);

关于c - gcc无法扩展某些宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44183934/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com