簡體   English   中英

gcc:通過顯式memcpy避免嚴格別名違規警告

[英]gcc: avoiding strict-aliasing violation warning by explicit memcpy

我有一個內存為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*否則強制轉換是未定義的行為。 例如,在加載值時可能存在體系結構特定的布局限制。 這就是我嘗試替代方法的原因。

這是簡化版的源代碼( 鏈接到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顯式復制數據來避免memcpy可以防止出現警告。 據我了解,它也應該是便攜式的。

查看匯編程序(gcc 7.2 with -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;
}

這是更新的godbolt鏈接 也應該是可移植的( sizeof(*this)是64位),所以我認為它是迄今為止最好的解決方案。

在C ++ 17中,可以使用memcmphas_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鏈接 ):

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

靜態斷言確保類X不包含填充位。 否則,比較兩個邏輯上等效的對象可能返回false,因為填充位的內容可能不同。 在這種情況下,在編譯時拒絕該代碼更安全。

(注意:據推測,C ++ 20將添加std :: bit_cast ,它可以用作memcmp的替代方案。但是,為了同樣的原因,你必須確保不涉及填充。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM