分配中的C ++ 17排序:是否仍未在GCC中实现? [英] C++17 sequencing in assignment: still not implemented in GCC?

查看:89
本文介绍了分配中的C ++ 17排序:是否仍未在GCC中实现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试以下代码作为幼稚的尝试,以实现ABGR字中的RB字节的交换

#include <stdio.h>
#include <stdint.h>

uint32_t ABGR_to_ARGB(uint32_t abgr)
{
  return ((abgr ^= (abgr >> 16) & 0xFF) ^= (abgr & 0xFF) << 16) ^= (abgr >> 16) & 0xFF;
}

int main() 
{
    uint32_t tmp = 0x11223344;
    printf("%x %x\n", tmp, ABGR_to_ARGB(tmp));
}

令我惊讶的是,此代码在C ++ 17模式下在GCC中工作"-字节已交换

http://coliru.stacked-crooked.com/a/43d0fc47f5539746

但是不应该交换字节! C ++ 17明确指出,分配的RHS应该在LHS之前[完全]排序,这也适用于复合分配.这意味着在上面的表达式中,每个^=的每个RHS都应使用abgr的原始值.因此,abgr中的最终结果应该只是将B字节与R字节进行异或.这就是Clang产生的结果(有趣的是,发出顺序警告)

http://coliru.stacked-crooked.com/a/eb9bdc8ced1b5f13

快速浏览GCC组装

https://godbolt.org/g/1hsW5a

表明它似乎是向后排序:在RHS之前是LHS.这是一个错误吗?还是这是GCC的某种有意识的决定?还是我误会了什么?

解决方案

int a = 1; (a += a) += a;表现出完全相同的行为,为此,GCC随后会计算a == 4,然后再按clang a == 3.

该标准的这一部分(源自N4762工作草案)产生了潜在的歧义:

[expr.ass]:7.6.18赋值和复合赋值运算符

第1段:赋值运算符(=)和复合赋值运算符都从右到左分组.都需要一个 可修改的左值作为其左操作数;它们的结果是一个指向左操作数的左值.总共的结果 如果左操作数是一个位字段,则cases是一个位字段.在所有情况下,赋值都会在值之后排序 左右操作数的计算,以及赋值表达式的值计算之前. 右操作数在左操作数之前排序.关于不确定顺序 函数调用,复合赋值的操作是单个评估.

第7段:形式为E1 op = E2的表达式的行为与E1 = E1 op E2等效,除了E1为 仅评估一次.在+ =和-=中,E1应该是算术类型,或者是一个指向可能的指针 cv限定的完全定义的对象类型.在其他所有情况下,E1都应为算术类型.

GCC似乎正在使用此规则从(a += a) += a(a = a + a) += aa = (a = a + a) + a进行内部转换(因为必须仅对a = a + a进行一次评估)-对于此表达式,正确应用了排序规则.

但是Clang似乎对最后一个转换步骤的处理方式有所不同:auto temp = a + a; temp = temp + a; a = temp;

尽管(根据原始代码),两个编译器都对此发出警告:

  • GCC:warning: operation on 'abgr' may be undefined [-Wsequence-point]

  • c语:warning: unsequenced modification and access to 'abgr' [-Wunsequenced]

因此,编译器作者知道这种歧义,因此决定以不同的优先级进行排序(GCC:第7段>第1段; clang:第1段>第7段).

这似乎是标准中的缺陷.

I tried the following code as a naive attempt to implement swapping of R and B bytes in an ABGR word

#include <stdio.h>
#include <stdint.h>

uint32_t ABGR_to_ARGB(uint32_t abgr)
{
  return ((abgr ^= (abgr >> 16) & 0xFF) ^= (abgr & 0xFF) << 16) ^= (abgr >> 16) & 0xFF;
}

int main() 
{
    uint32_t tmp = 0x11223344;
    printf("%x %x\n", tmp, ABGR_to_ARGB(tmp));
}

To my surprise this code "worked" in GCC in C++17 mode - the bytes were swapped

http://coliru.stacked-crooked.com/a/43d0fc47f5539746

But it is not supposed to swap bytes! C++17 clearly states that the RHS of assignment is supposed to be [fully] sequenced before the LHS, which applies to compound assignment as well. This means that in the above expression each RHS of each ^= is supposed to use the original value of abgr. Hence the ultimate result in abgr should simply have B byte XORed by R byte. This is what Clang appears to produce (amusingly, with a sequencing warning)

http://coliru.stacked-crooked.com/a/eb9bdc8ced1b5f13

A quick look at GCC assembly

https://godbolt.org/g/1hsW5a

reveals that it seems to sequence it backwards: LHS before RHS. Is this a bug? Or is this some sort of conscious decision on GCC's part? Or am I misunderstanding something?

解决方案

The exact same behavior is exhibited by int a = 1; (a += a) += a;, for which GCC calculates a == 4 afterwards and clang a == 3.

The underlying ambiguity arises from this part of the standard (from working draft N4762):

[expr.ass]: 7.6.18 Assignment and compound assignment operators

Paragraph 1: The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation.

Paragraph 7: The behavior of an expression of the form E1 op = E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once. In += and -=, E1 shall either have arithmetic type or be a pointer to a possibly cv-qualified completely-defined object type. In all other cases, E1 shall have arithmetic type.

GCC seems to be using this rule to internally transfrom (a += a) += a to (a = a + a) += a to a = (a = a + a) + a (since a = a + a has to be evaluated only once) - and for this expression the sequencing rules are correctly applied.

Clang however seems to do that last transformation step differently: auto temp = a + a; temp = temp + a; a = temp;

Both compilers give a warning about this, though (from the original code):

  • GCC: warning: operation on 'abgr' may be undefined [-Wsequence-point]

  • clang: warning: unsequenced modification and access to 'abgr' [-Wunsequenced]

So the compiler writers know about this ambiguity and decided to prioritize differently (GCC: Paragraph 7 > Paragraph 1; clang: Paragraph 1 > Paragraph 7).

This seems to be a defect in the standard.

这篇关于分配中的C ++ 17排序:是否仍未在GCC中实现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆