【问题标题】:Pre-Increment Operators when Using the Variable on the Same Line在同一行上使用变量时的预增量运算符
【发布时间】:2014-08-11 04:05:25
【问题描述】:

我 - 相信 - 我正在尝试做的事情可能是有效的,因为它在两种情况下都用逗号分隔(不是典型的分配),但我不确定并且搜索没有提出任何内容关于这两种具体情况。

在这两种情况下,我都将变量用作两个并行数组的索引。

int a[3] = {10, 20, 30};
int b[3] = {20, 40, 60};

情况 #1:初始化数组的结构

struct testStruct {
     int t1;
     int t2;
};
int i = 0;
testStruct test = {a[++i], b[i]}

最后一行的预期结果:test = {20, 40}

情况 #2:将数组中的特定值作为函数 args 传递

void testFunc(int t1, int t2) {
    // do stuff
}
int i = 0;
test(a[++i], b[i]);

最后一行的预期结果:test(20, 40)

这是有效的代码吗?如果是,它在所有编译器中都有效吗?

结果是我所期望的吗?如果是这样,是因为数组还是因为逗号?

谢谢!

【问题讨论】:

标签: c++ undefined-behavior pre-increment


【解决方案1】:

我建议不要在您的代码中长期使用此类“技巧”,这是维护的噩梦,而且很难推理。几乎总是有替代方案,例如以下代码:

testStruct test = {a[++i], b[i]}

可以改为:

++i ;
testStruct test = {a[i], b[i]}

话虽如此,这两种情况都没有在函数调用和初始化列表中使用逗号运算符,逗号是语法元素,仅此而已。

您的第一种情况已经明确定义,尽管根据这是 C++11 还是 C++11 之前的版本,有一些警告。

在这两种情况下,每个逗号后面都有一个序列点,尽管 C++11 之前没有指定求值顺序。因此,我们可以通过转到defect report 430 来查看 C++11 之前的情况,它说:

最近的 GCC 错误报告 ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) 询问 有效期

int count = 23;   int foo[] = { count++, count++, count++ };

这是未定义或未指定还是其他?

答案是(强调我的未来):

我相信标准是明确的,每个初始化表达式 以上是一个完整的表达式(1.9 [intro.execution]/12-13;另见 问题392),因此每个之后都有一个序列点 表达式(1.9 [intro.execution]/16)。我同意标准确实 似乎没有规定表达式的计算顺序, 也许应该。有谁知道编译器不会 从左到右计算表达式?

在 C++11 中,它是在 draft C++11 standard 部分的 8.4.5 段落中烘焙的,上面写着:

在花括号初始化列表的初始化列表中, 初始化子句,包括任何由包扩展产生的子句 (14.5.3),按照它们出现的顺序进行评估。那是, 与给定关联的每个值计算和副作用 初始化子句在每个值计算之前排序,并且 与任何紧随其后的初始化子句相关的副作用 初始化列表的逗号分隔列表。

我坚持使用 C++11,因为它不会改变其余内容的答案,尽管关于排序的措辞确实有所不同,结论是相同的。

第二种情况调用undefined behavior,因为函数参数的求值顺序是未指定的,并且它们的求值相对于彼此的顺序是不确定的。我们可以从1.9 段落 15 中看到这种未定义的行为:

除非另有说明,否则对单个运算符的操作数进行求值 和单个表达式的子表达式是无序的。 [ 注意:在计算过程中多次计算的表达式中 程序的执行,未排序和不确定排序 不需要一致地执行其子表达式的评估 在不同的评价中。 —尾注] 的价值计算 运算符的操作数在计算值之前排序 运算符的结果。 如果标量对象的副作用是 相对于同一标量上的另一个副作用而言是无序的 使用相同标量的值的对象或值计算 对象,行为未定义

并提供以下示例:

f(i = -1, i = -1); // the behavior is undefined

【讨论】:

    【解决方案2】:

    第一个代码sn -p

    testStruct test = {a[++i], b[i]};
    

    有效(如果要添加分号),因为根据 C++ 标准

    4 在花括号初始化列表的初始化列表中, 初始化子句,包括任何由包扩展产生的子句 (14.5.3),按照它们出现的顺序进行评估。那是, 与给定关联的每个值计算和副作用 初始化子句在每个值计算之前排序,并且 与任何紧随其后的初始化子句相关的副作用 初始化列表的逗号分隔列表。

    考虑到编译器 GCC 4.8.1 有(或至少有)我描述的错误

    here 虽然描述是用俄语写的,但您可以使用 goofle "translate" 服务进行翻译

    但是第二个代码sn -p

    test(a[++i], b[i]);
    

    具有未定义的行为,因为未指定函数参数的求值顺序。

    你可以改写

    test( { a[++i], b[i] } );
    

    在这种情况下,表达式将是格式正确的。

    【讨论】:

    • Unspecified 保持开放状态,它们可能被不确定地排序。我想你的意思是说函数参数是unsequenced。 (1.9p13)。
    • @ecatmur 我的意思是说参数的评估顺序是未指定的。这会导致未定义的行为,因为拥有两个编译器可以获得不同的结果。结果未定义。
    • 未指定行为不同于未定义行为(1.3.24 与 1.3.25)。在这种情况下,行为是未定义的,而不是未指定的。
    • @ecatmur 未指定的行为可能导致未定义的行为,因为在此示例中存在副作用。
    • 此示例中导致未定义行为的原因是因为评估未排序,而不是因为未指定顺序。未指定的评估顺序完全有可能导致已定义但未指定的行为,例如在int i = 1; [](int, int){}([&](){return i+=2;}(), [&](){return i*=3;}()); 中,i 的结果值必须是 59,但未指定哪个。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-14
    • 2023-04-01
    • 1970-01-01
    • 2019-09-25
    • 2017-07-13
    • 2017-06-30
    • 1970-01-01
    相关资源
    最近更新 更多