あなたの質問は単にこれだったようです:「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,%rbx
new の呼び出し後)
---- 編集 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
もはや「ホット」ではない可能性があります。
別の言い方をすれば、スタックを乱用すると、競合するリソースになり、ヒープの使用に対する利点が取り残されるか、排除されます。