web-dev-qa-db-ja.com

コンパイラは、文字を追加するときに未使用の文字列の最適化を停止します

次のコードがなぜなのか興味があります。

#include <string>
int main()
{
    std::string a = "ABCDEFGHIJKLMNO";
}

-O3でコンパイルすると、次のコードが生成されます。

main:                                   # @main
    xor     eax, eax
    ret

(私は未使用のaが必要ないことを完全に理解しているので、コンパイラは生成されたコードからそれを完全に省略することができます)

ただし、次のプログラム:

#include <string>
int main()
{
    std::string a = "ABCDEFGHIJKLMNOP"; // <-- !!! One Extra P 
}

利回り:

main:                                   # @main
        Push    rbx
        sub     rsp, 48
        lea     rbx, [rsp + 32]
        mov     qword ptr [rsp + 16], rbx
        mov     qword ptr [rsp + 8], 16
        lea     rdi, [rsp + 16]
        lea     rsi, [rsp + 8]
        xor     edx, edx
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
        mov     qword ptr [rsp + 16], rax
        mov     rcx, qword ptr [rsp + 8]
        mov     qword ptr [rsp + 32], rcx
        movups  xmm0, xmmword ptr [rip + .L.str]
        movups  xmmword ptr [rax], xmm0
        mov     qword ptr [rsp + 24], rcx
        mov     rax, qword ptr [rsp + 16]
        mov     byte ptr [rax + rcx], 0
        mov     rdi, qword ptr [rsp + 16]
        cmp     rdi, rbx
        je      .LBB0_3
        call    operator delete(void*)
.LBB0_3:
        xor     eax, eax
        add     rsp, 48
        pop     rbx
        ret
        mov     rdi, rax
        call    _Unwind_Resume
.L.str:
        .asciz  "ABCDEFGHIJKLMNOP"

同じ-O3でコンパイルした場合。文字列が1バイト長いにもかかわらず、aが未使用であることを認識しない理由がわかりません。

この質問は、gcc 9.1およびclang 8.0に関連しています(オンライン: https://gcc.godbolt.org/z/p1Z8Ns )。私の観察の他のコンパイラは、未使用の変数(ellcc)を完全に削除するためです。または、文字列の長さに関係なくコードを生成します。

70
Ferenc Deak

受け入れられた回答は有効ですが、C++ 14以降では、実際にnewおよびdeletecanを呼び出すことが当てはまります。離れて最適化されます。 cppreferenceでこの難解な表現を参照してください。

新しい式では、置き換え可能な割り当て関数によって行われた...割り当てを省略できます。省略の場合、ストレージは、割り当て関数を呼び出さずにコンパイラーによって提供されます(これにより、未使用の新しい式を最適化することもできます)。

...

この最適化はnew-expressionsが使用される場合にのみ許可され、置換可能な割り当て関数を呼び出す他のメソッドは許可されないことに注意してください:delete[] new int[10];は最適化できますが、演算子delete(operator new(10));はできません。

これにより、非常に長くても、コンパイラーはローカルのstd::stringを完全に削除できます。実際、libc ++を使用したclang ++ すでにこれを行っています (GodBolt)。libc++は__newの実装で組み込み__deleteおよびstd::stringを使用するため、これは「コンパイラーによって提供されるストレージ」です。したがって、次のようになります。

main():
        xor eax, eax
        ret

基本的に任意の長さの未使用の文字列を使用します。

GCCは対応していませんが、最近これに関するバグレポートを公開しました。リンクについては this SO answer を参照してください。

0
einpoklum