gpt4 book ai didi

c++ - 这两个函数调用在内存管理方面的差异?

转载 作者:行者123 更新时间:2023-11-30 02:51:06 26 4
gpt4 key购买 nike

假设 ptr 是一个指向 T1 类型对象的指针,而 inst 是一个 T2 类型的实例:

T1* ptr(new T1);
T2 inst;

我相应地设计了 T1T2 的方法,这意味着在 T1 中我几乎只有 void 将对 this 对象和 T2 对象进行操作的函数 我将拥有访问实际成员的方法。所以我终于像这样打了 2 个电话:

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

考虑到这两个主要区别(指针与实例和实际调用 ->. )以及可能使用 this成员值,在多线程和高性能环境下,ptrinst上强加的内存模型是一样的吗?上下文切换、堆栈创建/分配、访问值等的成本如何?

编辑:

奇怪的是,没有人将分配器称为可以改变分配或位置游戏的新玩家。

我想把重点放在内存模型上,硬件内部的工作方式(主要是 x86 和 ARM)。

最佳答案

您的问题似乎很简单:调用“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() 实例,它必须处理这两种情况,因此不可能有任何区别。

唯一的变化,如果有的话,是在调用站点。当调用 dynamic->Bar() 时,编译器将发出等同于 this = dynamic; 的代码。调用 Foo0Bar 将“dynamic”的值直接传输到“this”所在的位置(寄存器/地址)。在 concrete.Bar 的情况下,具体将在堆栈上,因此它会发出略有不同的代码以将堆栈偏移量加载到相同的寄存器/内存位置并进行调用。函数本身将无从得知。

---- 编辑----

这是来自“g++ -Wall -o test.exe -O1 test.cpp && objdump -lsD test.exe | c++filt”的程序集,上面的代码集中在 main 上:

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”,第一个使用堆栈相关地址(因为 concrete 在堆栈上),第二个简单地复制“rbx”的值",它具有之前“new”的返回值(mov %rax,%rbx 在调用 new 之后)

---- 编辑 2 ----

除了函数调用本身,就必须在对象内构造、拆除和访问值的实际操作而言,堆栈通常更快。

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

通常比

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

因为第二个变体必须分配内存,而且一般来说,内存分配器很慢(它们通常有某种锁来管理共享内存池)。此外,为此分配的内存可能是缓存冷的(尽管大多数库存分配器会将 block 数据写入页面,导致它在您开始使用它时变得有点缓存热,但这可能会导致页面错误,或从缓存中推出其他内容)。

使用堆栈的另一个潜在优势是通用缓存一致性。

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 和 f3 的堆栈区域可能不再“热” .

换句话说:如果滥用堆栈,它也会成为有争议的资源,并且相对于堆使用的优势会被边缘化或消除。

关于c++ - 这两个函数调用在内存管理方面的差异?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19947370/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com