1

ptrは type のオブジェクトへのポインターであり、 typeT1instインスタンスであると仮定しT2ます。

T1* ptr(new T1);
T2 inst;

T1それに応じてのメソッドを設計しますT2。つまり、T1ほとんどの場合void、オブジェクトを操作する関数のみがthisあり、内部T2には実際のメンバーにアクセスするメソッドがあります。だから私は最終的に次のように2つの呼び出しを行います:

ptr->doSomething();
inst.doSomething();

これら 2 つの主な違い (ポインターとインスタンス、および実際の呼び出しと->vs .) と、おそらく vs の使用をthis考慮するmember valuesと、マルチスレッドで高性能な環境では、メモリ モデルはptrinst同じですか? コンテキストの切り替え、スタックの作成/割り当て、値へのアクセスなどのコストはどうですか?

編集:

割り当てや地域のゲームを変えることができる新しいプレーヤーとしてアロケーターについて誰も言及していないのは奇妙なことです。

これをメモリ モデル、つまりハードウェア (主に x86 と ARM ) 内でどのように動作しているかに焦点を当てたいと思います。

4

2 に答える 2

3

あなたの質問は単にこれだったようです:「ptr->something()」と「instance.something()」の呼び出しの違いは何ですか?

機能「何か」の観点からは、絶対に何もありません。

#include <iostream>

struct Foo {
    void Bar(int i) { std::cout << i << "\n"; }
};

int main() {
    Foo concrete;
    Foo* dynamic = new Foo;

    concrete.Bar(1);
    dynamic->Bar(2);

    delete dynamic;
}

コンパイラは、両方のケースを処理する必要がある Foo::Bar() の 1 つのインスタンスのみを発行するため、違いはありません

唯一の変更は、もしあれば、呼び出しサイトにあります。コンパイラを呼び出すと、"dynamic" の値を "this" が保持されている場所 (レジスタ/アドレス) に直接転送するのdynamic->Bar()と同等のコードが出力されます。this = dynamic; call Foo0Barの場合concrete.Bar、concrete はスタック上にあるため、スタック オフセットを同じレジスタ/メモリ位置にロードして呼び出しを行うために、わずかに異なるコードを出力します。関数自体にはわかりません。

- - 編集 - -

メインに焦点を当てた、上記のコードを使用した "g++ -Wall -o test.exe -O1 test.cpp && objdump -lsD test.exe | c++filt" のアセンブリを次に示します。

main():
  400890:       53                      push   %rbx
  400891:       48 83 ec 10             sub    $0x10,%rsp
  400895:       bf 01 00 00 00          mov    $0x1,%edi
  40089a:       e8 f1 fe ff ff          callq  400790 <operator new(unsigned long)@plt>
  40089f:       48 89 c3                mov    %rax,%rbx
  4008a2:       be 01 00 00 00          mov    $0x1,%esi
  4008a7:       48 8d 7c 24 0f          lea    0xf(%rsp),%rdi
  4008ac:       e8 47 00 00 00          callq  4008f8 <Foo::Bar(int)>
  4008b1:       be 02 00 00 00          mov    $0x2,%esi
  4008b6:       48 89 df                mov    %rbx,%rdi
  4008b9:       e8 3a 00 00 00          callq  4008f8 <Foo::Bar(int)>
  4008be:       48 89 df                mov    %rbx,%rdi
  4008c1:       e8 6a fe ff ff          callq  400730 <operator delete(void*)@plt>
  4008c6:       b8 00 00 00 00          mov    $0x0,%eax
  4008cb:       48 83 c4 10             add    $0x10,%rsp
  4008cf:       5b                      pop    %rbx
  4008d0:       c3                      retq   

メンバー関数呼び出しは次のとおりです。

コンクリート.バー(1)

4008a2:       be 01 00 00 00          mov    $0x1,%esi
4008a7:       48 8d 7c 24 0f          lea    0xf(%rsp),%rdi
4008ac:       e8 47 00 00 00          callq  4008f8 <Foo::Bar(int)>

動的->バー(2)

4008b1:       be 02 00 00 00          mov    $0x2,%esi
4008b6:       48 89 df                mov    %rbx,%rdi
4008b9:       e8 3a 00 00 00          callq  4008f8 <Foo::Bar(int)>

明らかに「rdi」は「this」を保持するために使用されており、1 つ目は (concreteスタック上にあるため) スタック相対アドレスを使用し、2 つ目は単純に「rbx」の値をコピーします。これには「new」からの戻り値があります。以前に(mov %rax,%rbxnew の呼び出し後)

---- 編集 2 ----

関数呼び出し自体を超えて、オブジェクト内の構築、分解、および値へのアクセスを行う必要がある実際の操作について言えば、スタックは一般的に高速です。

{
    Foo concrete;
    foo.Bar(1);
}

一般に、

Foo* dynamic = new Foo;
dynamic->Bar(1);
delete dynamic;

2 番目のバリアントはメモリを割り当てる必要があり、一般に、メモリ アロケータは低速です (通常、共有メモリ プールを管理するために何らかのロックが設定されています)。また、これに割り当てられたメモリはキャッシュ コールドである可能性があります (ただし、ほとんどのストック アロケータはブロック データをページに書き込み、それを使用するようになるまでに多少キャッシュ ウォームになりますが、それによってページ フォールトが発生する可能性があります。またはキャッシュから何か他のものをプッシュします)。

スタックを使用するもう 1 つの潜在的な利点は、一般的なキャッシュの一貫性です。

int i, j, k;
Foo f1, f2, f3;
// ... thousands of operations populating those values
f1.DoCrazyMagic(f1, f2, f3, i, j, k);

内に外部参照がない場合DoCrazyMagic、すべての操作は小さなメモリ ローカリティ内で行われます。逆に言えば

int *i, *j, *k;
Foo *f1, *f2, *f3;
// ... thousands of operations populating those values
f1->DoCrazyMagic(*f1, *f2, *f3, *i, *j, *k);

複雑なシナリオでは、変数が複数のページに分散され、複数のページ フォールトが発生する可能性があります。

ただし、「数千の操作」が十分に強力で複雑な場合、配置するスタック領域はi, j, k, f1, f2 and f3もはや「ホット」ではない可能性があります。

別の言い方をすれば、スタックを乱用すると、競合するリソースになり、ヒープの使用に対する利点が取り残されるか、排除されます。

于 2013-11-13T08:07:51.957 に答える