内存布局 C++ 对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1632600/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:40:05  来源:igfitidea点击:

memory layout C++ objects

c++memoryobject

提问by rsinha

I am basically wondering how C++ lays out the object in memory. So, I hear that dynamic casts simply adjust the object's pointer in memory with an offset; and reinterpret kind of allows us to do anything with this pointer. I don't really understand this. Details would be appreciated!

我基本上想知道 C++ 如何在内存中布置对象。所以,我听说动态转换只是用偏移量调整内存中对象的指针;和 reinterpret 类型允许我们用这个指针做任何事情。我真的不明白这一点。细节将不胜感激!

采纳答案by Martin York

Each class lays out its data members in the order of declaration.
The compiler is allowed to place padding between members to make access efficient (but it is not allowed to re-order).

每个类都按照声明的顺序排列其数据成员。
允许编译器在成员之间放置填充以提高访问效率(但不允许重新排序)。

How dynamic_cast<>works is a compiler implementation detail and not defined by the standard. It will all depend on the ABI used by the compiler.

如何dynamic_cast<>发挥作用是编译器实现的细节,而不是由标准定义。这完全取决于编译器使用的 ABI。

reinterpret_cast<>works by just changing the type of the object. The only thing that you can guarantee that works is that casting a pointer to a void* and back to the same the pointer to class will give you the same pointer.

reinterpret_cast<>只需更改对象的类型即可工作。您唯一可以保证有效的是,将指向 void* 的指针转换为指向 class 的指针将给您相同的指针。

回答by Praxeolitic

Memory layout is mostly left to the implementation. The key exception is that member variables for a given access specifier will be in order of their declaration.

内存布局主要留给实现。关键的例外是给定访问说明符的成员变量将按照它们的声明顺序排列。

§ 9.2.14

§ 9.2.14

Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).

分配具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。具有不同访问控制的非静态数据成员的分配顺序未指定 (11)。实现对齐要求可能会导致两个相邻的成员不会立即被分配;管理虚拟函数 (10.3) 和虚拟基类 (10.1) 的空间需求也是如此。

Other than member variables, a class or struct needs to provide space for member variables, subobjects of base classes, virtual function management (e.g. a virtual table), and padding and alignment of these data. This is up to the implementation but the Itanium ABI specification is a popular choice. gcc and clang adhere to it (at least to a degree).

除了成员变量,类或结构体还需要为成员变量、基类的子对象、虚函数管理(如虚表)以及这些数据的填充和对齐提供空间。这取决于实现,但 Itanium ABI 规范是一个流行的选择。gcc 和 clang 坚持它(至少在一定程度上)。

http://mentorembedded.github.io/cxx-abi/abi.html#layout

http://mentorembedded.github.io/cxx-abi/abi.html#layout

The Itanium ABI is of course not part of the C++ standard and is not binding. To get more detailed you need to turn to your implementor's documentation and tools. clang provides a tool to view the memory layout of classes. As an example, the following:

Itanium ABI 当然不是 C++ 标准的一部分,也没有约束力。要获得更详细的信息,您需要查阅实现者的文档和工具。clang 提供了一个工具来查看类的内存布局。例如,以下内容:

class VBase {
    virtual void corge();
    int j;
};

class SBase1 {
    virtual void grault();
    int k;
};

class SBase2 {
    virtual void grault();
    int k;
};

class SBase3 {
    void grault();
    int k;
};

class Class : public SBase1, SBase2, SBase3, virtual VBase {
public:
    void bar();
    virtual void baz();
    // virtual member function templates not allowed, thinking about memory
    // layout and vtables will tell you why
    // template<typename T>
    // virtual void quux();
private:
    int i;
    char c;
public:
    float f;
private:
    double d;
public:
    short s;
};

class Derived : public Class {
    virtual void qux();
};

int main() {
    return sizeof(Derived);
}

After creating a source file that uses the memory layout of the class, clang will reveal the memory layout.

创建使用类的内存布局的源文件后,clang 将显示内存布局。

$ clang -cc1 -fdump-record-layouts layout.cpp

The layout for Class:

布局为Class

*** Dumping AST Record Layout
   0 | class Class
   0 |   class SBase1 (primary base)
   0 |     (SBase1 vtable pointer)
   8 |     int k
  16 |   class SBase2 (base)
  16 |     (SBase2 vtable pointer)
  24 |     int k
  28 |   class SBase3 (base)
  28 |     int k
  32 |   int i
  36 |   char c
  40 |   float f
  48 |   double d
  56 |   short s
  64 |   class VBase (virtual base)
  64 |     (VBase vtable pointer)
  72 |     int j
     | [sizeof=80, dsize=76, align=8
     |  nvsize=58, nvalign=8]

More on this clang feature can be found on Eli Bendersky's blog:

可以在 Eli Bendersky 的博客上找到有关此 clang 功能的更多信息:

http://eli.thegreenplace.net/2012/12/17/dumping-a-c-objects-memory-layout-with-clang/

http://eli.thegreenplace.net/2012/12/17/dumping-ac-objects-memory-layout-with-clang/

gcc provides a similar tool, `-fdump-class-hierarchy'. For the class given above, it prints (among other things):

gcc 提供了一个类似的工具,`-fdump-class-hierarchy'。对于上面给出的类,它打印(除其他外):

Class Class
   size=80 align=8
   base size=58 base align=8
Class (0x0x141f81280) 0
    vptridx=0u vptr=((& Class::_ZTV5Class) + 24u)
  SBase1 (0x0x141f78840) 0
      primary-for Class (0x0x141f81280)
  SBase2 (0x0x141f788a0) 16
      vptr=((& Class::_ZTV5Class) + 56u)
  SBase3 (0x0x141f78900) 28
  VBase (0x0x141f78960) 64 virtual
      vptridx=8u vbaseoffset=-24 vptr=((& Class::_ZTV5Class) + 88u)

It doesn't itemize the member variables (or at least I don't know how to get it to) but you can tell they would have to be between offset 28 and 64, just as in the clang layout.

它没有逐项列出成员变量(或者至少我不知道如何获得它),但是您可以看出它们必须在偏移量 28 和 64 之间,就像在 clang 布局中一样。

You can see that one base class is singled out as primary. This removes the need for adjustment of the thispointer when Classis accessed as an SBase1.

您可以看到一个基类被单挑为primary. 这消除了thisClass作为SBase1.

The equivalent for gcc is:

gcc 的等效项是:

$ g++ -fdump-class-hierarchy -c layout.cpp

The equivalent for Visual C++ is:

Visual C++ 的等效项是:

cl main.cpp /c /d1reportSingleClassLayoutTest_A

see: https://blogs.msdn.microsoft.com/vcblog/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022/

参见:https: //blogs.msdn.microsoft.com/vcblog/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022/

回答by Jason E

As stated previously, the full details are complicated, painful to read, and really only useful to compiler developers, and varies between compilers. Basically, each object contains the following (usually laid out in this order):

如前所述,完整的细节很复杂,读起来很痛苦,而且实际上只对编译器开发人员有用,并且因编译器而异。基本上,每个对象都包含以下内容(通常按此顺序排列):

  1. Runtime type information
  2. Non-Virtual base objects and their data (probably in order of declaration).
  3. Member variables
  4. Virtual base objects and their data (Probably in some DFS tree search order).
  1. 运行时类型信息
  2. 非虚拟基础对象及其数据(可能按声明顺序)。
  3. 成员变量
  4. 虚拟基础对象及其数据(可能按某种 DFS 树搜索顺序)。

These pieces of data may or may not be padded to make memory alignment easier etc. Hidden in the runtime type information is stuff about the type, v-tables for virtual parent classes etc, all of which is compiler specific.

这些数据可能会或可能不会被填充以使内存对齐更容易等。隐藏在运行时类型信息中的是关于类型、虚拟父类的 v 表等的内容,所有这些都是特定于编译器的。

When it comes to casts, reinterpret_castsimply changes the C++ data type of the pointer and does nothing else, so you had better be sure you know what you're doing when you use it, otherwise you're liable to mess things up badly. dynamic_castdoes very much the same thing as static_cast (in altering the pointer) except it uses the runtime type information to figure out if it can cast to the given type, and how to do so. Again, all that is compiler specific. Note that you can't dynamic_casta void*because it needs to know where to find the runtime type information so it can do all its wonderful runtime checks.

当涉及到强制转换时,reinterpret_cast只需更改指针的 C++ 数据类型,什么都不做,因此您最好确保在使用它时知道自己在做什么,否则很容易把事情搞砸。dynamic_cast除了它使用运行时类型信息来确定它是否可以转换为给定类型以及如何转换之外,它与 static_cast 的作用非常相似(改变指针)。同样,所有这些都是特定于编译器的。请注意,您不能这样 dynamic_cast做,void*因为它需要知道在哪里可以找到运行时类型信息,以便它可以执行所有出色的运行时检查。

回答by Hyman Rosen

The answer is, "it's complicated". Dynamic cast does not simply adjust pointers with an offset; it may actually retrieve internal pointers inside the object in order to do its work. GCC follows an ABI designed for Itanium but implemented more broadly. You can find the gory details here: Itanium C++ ABI.

答案是“这很复杂”。动态转换不是简单地用偏移量调整指针;它实际上可能会检索对象内部的内部指针以完成其工作。GCC 遵循为 Itanium 设计但实施范围更广的 ABI。你可以在这里找到血腥的细节:Itanium C++ ABI

回答by cppminds

this question is already answered at http://dieharddeveloper.blogspot.in/2013/07/c-memory-layout-and-process-image.htmlhere is a excerpt from there: In the middle of the process's address space, there is a region is reserved for shared objects. When a new process is created, the process manager first maps the two segments from the executable into memory. It then decodes the program's ELF header. If the program header indicates that the executable was linked against a shared library, the process manager (PM) will extract the name of the dynamic interpreter from the program header. The dynamic interpreter points to a shared library that contains the runtime linker code.

这个问题已经在http://dieharddeveloper.blogspot.in/2013/07/c-memory-layout-and-process-image.html 上得到了回答, 这里是一个摘录:在进程地址空间的中间,有是为共享对象保留的区域。创建新进程时,进程管理器首先将可执行文件中的两个段映射到内存中。然后它解码程序的 ELF 头。如果程序头指示可执行文件链接到共享库,进程管理器 (PM) 将从程序头中提取动态解释器的名称。动态解释器指向一个包含运行时链接器代码的共享库。