245

関数内に変数(たとえば、大きな配列)がある場合、それを宣言することは意味がstaticありconstexprますか?constexpr配列がコンパイル時に作成されることを保証するので、static役に立たないでしょうか?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

生成されたコードまたはセマンティクスに関して、static実際にそこで何かを行っていますか?

4

3 に答える 3

295

簡単に言えばstatic、それは便利であるだけでなく、常に望まれることになるでしょう。

staticまず、とconstexprは互いに完全に独立していることに注意してください。static実行中のオブジェクトの存続期間を定義します。constexprオブジェクトがコンパイル中に使用可能である必要があることを指定します。コンパイルと実行は、時間と空間の両方で互いに素であり、互いに素です。したがって、プログラムがコンパイルされると、constexpr関連性がなくなります。

宣言されたすべての変数constexprは暗黙的にconstですがconststaticほぼ直交しています(static const整数との相互作用を除く)。

C++オブジェクトモデル(§1.9)では、ビットフィールド以外のすべてのオブジェクトが少なくとも1バイトのメモリを占有し、アドレスを持っている必要があります。さらに、特定の時点でプログラムで監視可能なそのようなオブジェクトはすべて、別個のアドレスを持っている必要があります(段落6)。これは、ローカルの非静的const配列を使用して関数を呼び出すたびに、コンパイラがスタック上に新しい配列を作成する必要はありません。コンパイラは、as-if他のそのようなオブジェクトができないことを証明できれば、原則として避難することができるからです。観察された。

残念ながら、配列は多かれ少なかれアドレスであるため、関数が自明でない限り(たとえば、本体が変換ユニット内に表示されない他の関数を呼び出さない限り)、それを証明するのは簡単ではありません。したがって、ほとんどの場合、非静的const(expr)配列は呼び出しのたびにスタック上に再作成する必要があります。これにより、コンパイル時に配列を計算できるという点が無効になります。

一方、ローカルstatic constオブジェクトはすべてのオブザーバーによって共有され、さらに、それが定義されている関数が呼び出されない場合でも初期化される可能性があります。したがって、上記のいずれも当てはまりません。コンパイラは、そのインスタンスを1つだけ生成するだけでなく自由です。読み取り専用ストレージにそのインスタンスを1つ自由に生成できます。

したがって、例では間違いなく使用する必要がstatic constexprあります。

ただし、を使用したくない場合が1つありますstatic constexprconstexpr宣言されたオブジェクトがODRで使用されるか、宣言されていない限り、staticコンパイラーはそれをまったく含めないでください。constexprこれは、コンパイルされたプログラムを不要なバイトで汚染することなく、コンパイル時の一時配列を使用できるため、非常に便利です。その場合、実行時にオブジェクトが強制的に存在する可能性があるためstatic、明らかに使用したくないでしょう。static

于 2012-12-13T20:12:15.600 に答える
22

与えられた答えに加えて、コンパイラがコンパイル時に変数を初期化する必要がないことに注意する価値があります。とconstexprの違いは、変数が一度だけ初期化されることを保証することです。constexprstatic constexprstatic constexpr

次のコードは、constexpr変数が複数回(ただし同じ値で)初期化される方法を示していますが、static constexpr確実に1回だけ初期化されます。

さらに、このコードは、との組み合わせconstexprに対するの利点を比較します。conststatic

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

可能なプログラム出力:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

ご覧のとおり、キーワードは初期化が1回だけ実行されるようにしconstexprながら、自分自身が複数回初期化されます(アドレスは同じではありません) 。static

于 2019-10-10T18:23:32.760 に答える
10

大規模なアレイを作成しないことは、パフォーマンスに劇的な影響を及ぼし、多くの最適化の失敗につながる可能性があるstatic場合でもです。constexprコードの速度が桁違いに遅くなる可能性があります。変数はまだローカルであり、コンパイラは、実行可能ファイルにデータとして格納するのではなく、実行時に変数を初期化することを決定する場合があります。

次の例を考えてみましょう。

template <int N>
void foo();

void bar(int n)
{
    // array of four function pointers to void(void)
    constexpr void(*table[])(void) {
        &foo<0>,
        &foo<1>,
        &foo<2>,
        &foo<3>
    };
    // look up function pointer and call it
    table[n]();
}

おそらく、テーブルからフェッチするアドレスgcc-10 -O3にコンパイルbar()することを期待しますjmpが、それは起こりません。

bar(int):
        mov     eax, OFFSET FLAT:_Z3fooILi0EEvv
        movsx   rdi, edi
        movq    xmm0, rax
        mov     eax, OFFSET FLAT:_Z3fooILi2EEvv
        movhps  xmm0, QWORD PTR .LC0[rip]
        movaps  XMMWORD PTR [rsp-40], xmm0
        movq    xmm0, rax
        movhps  xmm0, QWORD PTR .LC1[rip]
        movaps  XMMWORD PTR [rsp-24], xmm0
        jmp     [QWORD PTR [rsp-40+rdi*8]]
.LC0:
        .quad   void foo<1>()
.LC1:
        .quad   void foo<3>()

これは、GCCがtable実行可能ファイルのデータセクションに格納しないことを決定し、代わりに関数が実行されるたびにその内容でローカル変数を初期化するためです。実際、constexprここで削除すると、コンパイルされたバイナリは100%同一になります。

これは、次のコードよりも簡単に10倍遅くなる可能性があります。

template <int N>
void foo();

void bar(int n)
{
    static constexpr void(*table[])(void) {
        &foo<0>,
        &foo<1>,
        &foo<2>,
        &foo<3>
    };
    table[n]();
}

私たちの唯一の変更は、私たちが行っtable staticたことですが、その影響は甚大です。

bar(int):
        movsx   rdi, edi
        jmp     [QWORD PTR bar(int)::table[0+rdi*8]]
bar(int)::table:
        .quad   void foo<0>()
        .quad   void foo<1>()
        .quad   void foo<2>()
        .quad   void foo<3>()

結論として、ルックアップテーブルをローカル変数にしないでくださいconstexpr。Clangは実際にはそのようなルックアップテーブルをうまく最適化しますが、他のコンパイラーはそうではありません。実例については、コンパイラエクスプローラを参照してください

于 2021-03-06T18:14:13.237 に答える