簡體   English   中英

為什么 c++ 編譯器不替換對 const class 成員的訪問,其值在編譯時已知?

[英]Why don't c++ compilers replace this access to a const class member with its value known at compile time?

在這個代碼片段中,為什么 c++ 編譯器在編譯test()時不只返回 1,而是從 memory 中讀取值?

struct Test {
  const int x = 1;

  // Do not allow initializing x with a different value
  Test() {}
};
int test(const Test& t) {
  return t.x; 
}

golbolt 上的代碼

編譯器 output:

test(Test const&):                         # @test(Test const&)
    mov     eax, dword ptr [rdi]
    ret

我本來期望:

test():                               # @test(Test const&)
    mov     eax, 1
    ret

是否有任何符合標准的方法來修改Test::x的值以包含與1不同的值? 還是允許編譯器進行此優化,但 gcc 和 clang 都沒有實現它?

編輯:當然,您立即發現我的錯誤是將此作為最小示例,即允許對結構進行聚合初始化。 我用一個空的默認構造函數更新了代碼,以防止這種情況發生。 godbolt 上的舊代碼

我相信這是因為您仍然可以使用其他 x 值構造一個 Test 實例,並使用如下初始化器列表:

Test x{2};
cout << test(x);

演示: https://www.ideone.com/7vlCmX

現在,您已不允許使用構造函數創建具有不同x值的Test object 實例,但 gcc/clang 仍未優化。

使用char*memcpy創建具有不同x值的Test object 的對象表示可能是合法的。 這將使優化非法。

例如,

   Test foo;
   char buf[sizeof(foo)];
   memcpy(buf, &foo, sizeof(foo));
   buf[0] = 3;         // on a little-endian system like x86, this is buf.x = 3;  - the upper bytes stay 0
   memcpy(&foo, buf, sizeof(foo));

唯一有問題的步驟是最后的memcpy回到foo 這就是創建帶有構造函數無法生成的x值的Test object 的原因。 在 C++ 的引用上下文中, const表示您不能通過引用修改此 object。 我不知道這如何適用於非const object 的const成員

We can see from this example that GCC and clang leave room for non-inline function calls to modify that member of an already-constructed Test object:

void ext(void*);  // might do anything to the pointed-to memory

int test() {
    Test foo;    // construct with x=1
    ext (&foo);
    return foo.x;
}

神螺栓

# GCC11.2 -O3.  clang is basically equivalent.
test():
        sub     rsp, 24             # stack alignment + wasted 16 bytes
        lea     rdi, [rsp+12]
        mov     DWORD PTR [rsp+12], 1      # construct with x=1
        call    ext(void*)
        mov     eax, DWORD PTR [rsp+12]    # reload from memory, not mov eax, 1
        add     rsp, 24
        ret

它可能是也可能不是錯過的優化。 許多錯過的優化是編譯器不尋找的東西,因為它的計算成本很高(即使是提前編譯器也不能在潛在的大函數上粗心地使用指數時間算法)。

不過,這似乎並不太昂貴,只需檢查構造函數默認值是否無法被覆蓋。 盡管在制作更快/更小的代碼方面它的價值似乎很低,因為希望大多數代碼不會這樣做。

這肯定是編寫代碼的次優方式,因為您在保持此常量的 class 的每個實例中都在浪費空間。 所以希望它不會經常出現在真正的代碼庫中。 static constexpr是慣用的,並且const per-instance 成員 object 好得多,如果您有意使用每個類常量。

然而,恆定傳播可能非常有價值,因此即使它很少發生,它也可以在它確實發生的情況下開啟重大優化。

要進行此優化,您需要告訴編譯器x不能更改:

struct Test {
    constexpr
    static int x = 1;
};

int test(const Test& t) {
    return t.x;
}

神栓 output

test(Test const&):                         # @test(Test const&)
        mov     eax, 1
        ret

在您的情況下,這意味着您有一個不可修改的變量,如果沒有通過任何其他方法給出,它將被設置為給定值。 但至少還有其他兩種方法,例如:

struct X {
    const int y = 1;
};
int test(const X& t) {
    return t.y;
}

struct Y: public X
{
    Y():X{9}{}
};

int main()
{
    X x1{3};
    std::cout << test(x1) << std::endl;

    Y y1{};
    std::cout << test(y1) << std::endl;

}

看到它工作

如果你想說:我的類型總是有相同的常數,你只需寫static constexpr int x = 1; 這是一個完全不同的語義。 如果你這樣做,組裝將是你所期望的。

暫無
暫無

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

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