【问题标题】:gcc: avoiding strict-aliasing violation warning by explicit memcpygcc:通过显式 memcpy 避免严格混叠违规警告
【发布时间】:2018-01-02 12:17:07
【问题描述】:

我有一个类占用 64 位内存。为了实现平等,我使用了reinterpret_cast<uint64_t*>,但它在 gcc 7.2(但不是 clang 5.0)上导致了这个警告:

$ g++ -O3 -Wall -std=c++17 -g -c example.cpp 
example.cpp: In member function ‘bool X::eq_via_cast(X)’:
example.cpp:27:85: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x);                                                                                     ^

据我了解,除非您要转换为实际类型或 char*,否则转换是未定义的行为。例如,加载值时可能存在特定于架构的布局限制。这就是我尝试其他方法的原因。

这里是简化版的源代码(link to godbolt):

#include <cstdint>
#include <cstring>

struct Y
{
    uint32_t x;
    bool operator==(Y y) { return x == y.x; }
};

struct X
{
    Y a;
    int16_t b;
    int16_t c;

    uint64_t to_uint64() {
        uint64_t result;
        std::memcpy(&result, this, sizeof(uint64_t));
        return result;
    }

    bool eq_via_memcpy(X x) {
        return to_uint64() == x.to_uint64();
    }

    bool eq_via_cast(X x) {
        return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x);
    }

    bool eq_via_comparisons(X x) {
        return a == x.a && b == x.b && c == x.c;
    }
};
static_assert(sizeof(X) == sizeof(uint64_t));

bool via_memcpy(X x1, X x2) {
    return x1.eq_via_memcpy(x2);
}

bool via_cast(X x1, X x2) {
    return x1.eq_via_cast(x2);
}

bool via_comparisons(X x1, X x2) {
    return x1.eq_via_comparisons(x2);
}

通过memcpy 显式复制数据来避免强制转换可防止警告。据我了解,它也应该是可移植的。

查看汇编程序(gcc 7.2 和 -std=c++17 -O3),memcpy 得到了完美优化,而直接比较导致代码效率降低:

via_memcpy(X, X):
  cmp rdi, rsi
  sete al
  ret

via_cast(X, X):
  cmp rdi, rsi
  sete al
  ret

via_comparisons(X, X):
  xor eax, eax
  cmp esi, edi
  je .L7
  rep ret
.L7:
  sar rdi, 32
  sar rsi, 32
  cmp edi, esi
  sete al
  ret

与 clang 5.0 (-std=c++17 -O3) 非常相似:

via_memcpy(X, X): # @via_memcpy(X, X)
  cmp rdi, rsi
  sete al
  ret

via_cast(X, X): # @via_cast(X, X)
  cmp rdi, rsi
  sete al
  ret

via_comparisons(X, X): # @via_comparisons(X, X)
  cmp edi, esi
  jne .LBB2_1
  mov rax, rdi
  shr rax, 32
  mov rcx, rsi
  shr rcx, 32
  shl eax, 16
  shl ecx, 16
  cmp ecx, eax
  jne .LBB2_3
  shr rdi, 48
  shr rsi, 48
  shl edi, 16
  shl esi, 16
  cmp esi, edi
  sete al
  ret
.LBB2_1:
  xor eax, eax
  ret
.LBB2_3:
  xor eax, eax
  ret

从这个实验来看,memcpy 版本似乎是代码性能关键部分的最佳方法。

问题:

  • memcpy 版本是可移植的 C++ 代码,我的理解是否正确?
  • 假设编译器能够像本例中那样优化 memcpy 调用是否合理?
  • 有没有我忽略的更好的方法?

更新:

正如 UKMonkey 所指出的,memcmp 在进行按位比较时更自然。它还可以编译成相同的优化版本:

bool eq_via_memcmp(X x) {
    return std::memcmp(this, &x, sizeof(*this)) == 0;
}

这里是updated godbolt link。还应该是可移植的(sizeof(*this) 是 64 位),所以我认为这是迄今为止最好的解决方案。

【问题讨论】:

  • 您依赖于内存中的特定结构布局,因此可能会限制可移植性。
  • 所以你真正想做的是按位比较类的内存;那恰好是64位?为什么不使用memcmp(this, other, sizeof(X))
  • 如果你想这样做,我建议至少断言has_unique_object_representations_v&lt;X&gt;
  • 请注意,一般来说,对结构使用 memcmp 是不安全的——它没有考虑填充位/字节的不确定值。
  • memcpy should be what you want 我们可能会在 C++20 中得到 bit_cast

标签: c++ gcc c++17 gcc-warning strict-aliasing


【解决方案1】:

在 C++17 中,memcmp 可以与has_unique_object_representations 结合使用:

bool eq_via_memcmp(X x) {
    static_assert(std::has_unique_object_representations_v<X>);
    return std::memcmp(this, &x, sizeof(*this)) == 0;
}

编译器应该能够将其优化为一个比较 (godbolt link):

via_memcmp(X, X):
  cmp rdi, rsi
  sete al
  ret

静态断言确保类X 不包含填充位。否则,比较两个逻辑上等价的对象可能会返回 false,因为填充位的内容可能不同。在这种情况下,在编译时拒绝该代码会更安全。

(注意:据推测,C++20 将添加std::bit_cast,它可以用作memcmp 的替代品。但是,出于同样的原因,您仍然必须确保不涉及填充。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-02-08
    • 2015-10-04
    • 2011-04-15
    • 2021-08-08
    • 1970-01-01
    • 2011-02-23
    • 2021-02-01
    • 1970-01-01
    相关资源
    最近更新 更多