在逗号运算符中,如果没有副作用,左操作数是否保证不会被实际执行?

Shi*_*zou 30 c c++ standards gcc

为了显示我将要使用C的主题,但是同样的宏也可以在C++中使用(有或没有struct),提出了相同的问题.

我想出了这个宏

#define STR_MEMBER(S,X) (((struct S*)NULL)->X, #X)
Run Code Online (Sandbox Code Playgroud)

它的目的是拥有const char*a的现有成员的strings()struct,这样如果该成员不存在,则编译失败.最小用法示例:

#include <stdio.h>

struct a
{
    int value;
};

int main(void)
{
    printf("a.%s member really exists\n", STR_MEMBER(a, value));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果value不是其成员struct a,代码将无法编译,这就是我想要的.

逗号运算符应该计算左操作数,然后丢弃表达式的结果(如果有的话),因此我的理解是,当左操作数的计算具有副作用时通常使用此运算符.

然而,在这种情况下,没有(预期的)副作用,但是当然如果编译器实际上没有产生评估表达式的代码,它将起作用,否则它将访问struct位于at NULL并且分段错误将发生.

Gcc/g ++ 6.3和4.9.2从未产生过那些危险的代码,即使-O0它们总是能够"看到"评估没有副作用,因此可以跳过它.

添加volatile宏(例如,因为访问该存储器地址是期望的副作用)是迄今为止触发分段错误的唯一方式.

所以问题是:C和C++语言标准中有什么保证编译器总是避免在编译器可以确定评估没有副作用时对逗号运算符的左操作数进行实际评估吗?

注意和修复

我不问了一个关于宏判断,因为它和使用它或使它更好的机会.出于这个问题的目的,宏是坏的,当且仅当它引起未定义的行为 - 即,当且仅当它有风险因为允许编译器生成"评估代码",即使这没有副作用.

我已经有两个明显的修复:"reifying" struct和使用offsetof.前者需要一个可访问的内存区域,与struct我们用作第一个参数的最大内容一样大STR_MEMBER(例如,静态联合可以做......).后者应该完美地工作:它给出了我们不感兴趣的偏移并避免访问问题 - 实际上我假设gcc,因为它是我使用的编译器(因此是标记),并且它的offsetof内置行为.

通过offsetof修复,宏变为

#define STR_MEMBER(S,X) (offsetof(struct S,X), #X)
Run Code Online (Sandbox Code Playgroud)

写volatile struct S而不是struct S不会导致段错误.

关于其他可能的"修复"的建议也是受欢迎的.

添加了备注

实际上,真正的用例是在静态存储中的C++中struct.这在C++中似乎很好,但是一旦我尝试使用更接近原始代码的C而不是为这个问题煮熟的代码,我意识到C对此并不满意:

error: initializer element is not constant
Run Code Online (Sandbox Code Playgroud)

C希望结构在编译时可以初始化,而不是C++就可以了.

M.M*_*M.M 17

C和C++语言标准中是否有任何内容可以保证编译器始终避免对逗号运算符的左操作数进行实际评估?

情况正好相反.该标准保证左操作数IS被评估(实际上它确实如此,没有任何例外).结果被丢弃了.


注意:对于左值表达式,"evaluate"并不意味着"访问存储的值".相反,它意味着找出指定的内存位置.包含左值表达式的其他代码可能会或可能不会继续访问存储器位置.从存储器位置读取的过程在C中称为"左值转换",或在C++中称为"左值到右值转换".

在C++中,丢弃值表达式(例如逗号运算符的左操作数)仅对其执行左值转换(如果是)volatile并且还满足其他一些条件(有关详细信息,请参阅C++ 14 [expr]/11) .在C值中,对于未使用结果的表达式(C11 6.3.2.1/2),确实发生了转换.

在您的示例中,无论左值转换是否发生都没有意义.在两种语言中X->Y,X指针在哪里定义为(*X).Y; 在C中,应用于*空指针的行为已经导致未定义的行为(C11 6.5.3/3),并且在C++中,.仅在左操作数实际指定对象的情况下定义运算符(C++ 14 [expr.定] /4.2).

  • 阅读它比写这些评论花费更少的时间...... (2认同)

Jus*_*tin 12

该逗号运算符(C文档,说非常类似的东西),有没有这样的保证.

在逗号表达式中E1, E2,表达式E1被评估,其结果被丢弃......,并且在评估表达式E2开始之前完成其副作用

无关的信息被省略

简单地说,E1将对其进行评估,尽管如果能够确定没有副作用,编译器可能会通过as-if规则对其进行优化.