2009-10-27 4 views
29

기본적으로 C++이 객체를 메모리에 배치하는 방법에 대해 궁금합니다. 그래서, 나는 동적 캐스팅이 단순히 오프셋을 사용하여 메모리에서 객체의 포인터를 조정한다는 것을 듣습니다. 종류의 재 해석은 우리가이 포인터로 무엇이든 할 수있게합니다. 나는 이것을 정말로 이해하지 못한다. 세부 사항은 평가 될 것이다!메모리 레이아웃 C++ 객체

답변

8

각 클래스는 선언 순서대로 데이터 멤버를 레이아웃합니다.
컴파일러는 효율적으로 액세스 할 수 있도록 멤버간에 패딩을 둘 수 있습니다 (하지만 재정렬은 허용되지 않습니다).

어떻게 dynamic_cast<> 작품은 컴파일러 구현 세부 사항이며 표준에 의해 정의되지 않았습니다. 그것은 모두 컴파일러에서 사용하는 ABI에 의존합니다.

reinterpret_cast<>은 개체 유형을 변경하여 작동합니다. 당신이 작동한다는 것을 보장 할 수있는 유일한 것은 void *에 대한 포인터를 캐스팅하고 클래스에 대한 동일한 포인터로 돌아 가면 동일한 포인터를 줄 것입니다.

+3

첫 번째 포인트는 완전히 정확하지 않습니다. 유일한 액세스 보장은 동일한 액세스 블록의 멤버가 정의 된 순서를 갖게된다는 것입니다. 이를 극단적으로 생각하고 싶다면 액세스 권한이 동일해도 주문이 더 이상 보장되지 않는다고 말할 수 있습니다. –

+0

@ 리차드. 내가 너를 이해하는지 모르겠다. 컴파일러는 요소를 재정렬 할 수 없습니다 (이것은 C와의 후방 호환성을위한 것입니다). 액세스 블록이란 무엇입니까? 정보를 얻고있는 표준의 올바른 부분을 지적 할 수 있습니까? –

+2

액세스 블록 (실제로는 액세스 지정자)은 클래스에있는'public :','private :','protected :'입니다. 저는이 기사가 http://www.embedded.com/design/218600150?pgno=1 매우 유용하다는 것을 알았습니다. 그게 제가 리차드가 그의 말에서 언급 한 것을 처음 배웠던 곳입니다. 거기에 링크 된 C++ 표준을 찾아 보았습니다. 198 (초 9.2 절 12) 상태 : "액세스 제어가 다른 비 정적 데이터 멤버의 할당 순서가 지정되지 않았습니다." –

4

대답은 "복잡합니다"입니다. 동적 캐스트는 단순히 오프셋을 사용하여 포인터를 조정하는 것이 아닙니다. 작업을 수행하기 위해 객체 내부의 내부 포인터를 실제로 검색 할 수 있습니다. GCC는 Itanium 용으로 설계된 ABI를 따르지만 더 광범위하게 구현됩니다. 여기서 자세한 내용을 확인할 수 있습니다 : Itanium C++ ABI.

4

앞에서 설명한 것처럼 전체 내용은 복잡하고 읽기가 힘들며 컴파일러 개발자에게만 유용하며 컴파일러에 따라 다릅니다. 기본적으로 각 개체는 (보통 순서로 배치) 다음이 포함

  1. 런타임 타입 정보
  2. 비 가상베이스 객체 및 데이터 (아마 선언의 순서대로).
  3. 구성원 변수
  4. 가상 기본 개체 및 해당 데이터 (일부 DFS 트리 검색 순서 일 가능성이 있음).

이러한 데이터는 메모리 정렬을 쉽게하기 위해 채워질 수도 있고 그렇지 않을 수도 있습니다. 런타임 유형 정보에는 유형, 가상 부모 클래스 용 v 테이블 등에 대한 정보가 숨겨져 있습니다. 모두 컴파일러 관련 정보입니다. .

캐스팅의 경우 reinterpret_cast은 단순히 포인터의 C++ 데이터 유형을 변경하고 다른 작업을 수행하지 않으므로 사용시 수행중인 작업을 잘 알고 있어야합니다. 그렇지 않으면 엉망이 될 가능성이 있습니다 나쁘게. dynamic_cast은 런타임 유형 정보를 사용하여 주어진 유형으로 변환 할 수 있는지 알아내는 방법과이를 수행하는 방법을 제외하고는 static_cast (포인터 변경시)와 매우 똑같습니다. 다시 말하지만, 컴파일러마다 다릅니다. dynamic_castvoid*은 런타임 유형 정보를 찾을 수있는 위치를 알아야하므로 모든 멋진 런타임 검사를 수행 할 수 있습니다.

1

이 질문에 이미 여기가에서 발췌 한 것입니다 http://dieharddeveloper.blogspot.in/2013/07/c-memory-layout-and-process-image.html 에 대한 답변 : 을 프로세스의 주소 공간의 중앙에서 지역 공유 객체를 위해 예약되어있다. 새로운 프로세스가 생성되면, 프로세스 관리자는 먼저 두 세그먼트를 실행 파일에서 메모리로 매핑합니다. 그런 다음 프로그램의 ELF 헤더를 디코딩합니다. 프로그램 헤더가 실행 파일이 공유 라이브러리에 링크 된 것으로 나타내면 프로세스 관리자 (PM)는 프로그램 헤더에서 동적 인터프리터의 이름을 추출합니다. 동적 해석기는 런타임 링커 코드가 포함 된 공유 라이브러리를 가리 킵니다.

+1

귀중한 정보이지만 다른 질문에 답합니다. – domen

9

메모리 레이아웃은 대부분 구현에 맡겨져 있습니다. 중요한 예외는 지정된 액세스 지정자에 대한 멤버 변수가 선언 순서대로 존재한다는 것입니다. 이후 부재 클래스 오브젝트 내의 높은 주소를 갖도록

§ 동일한 액세스 제어 (11.)와 (비 연합) 클래스 9.2.14

비 정적 데이터 멤버가 할당된다. 서로 다른 접근 통제를 가진 비 정적 (non-static) 데이터 구성원들의 할당 순서는 명기되어 있지 않다. 구현 정렬 요구 사항으로 인해 인접한 두 멤버 이 서로 즉시 할당되지 않을 수 있습니다. 가상 기능 (10.3) 및 가상 기본 클래스 (10.1)를 관리하기위한 공간에 대한 요구 사항은 일 수 있습니다.

멤버 변수 이외에 클래스 또는 구조체는 멤버 변수, 기본 클래스의 하위 개체, 가상 함수 관리 (예 : 가상 테이블) 및 이러한 데이터의 패딩 및 정렬을위한 공간을 제공해야합니다. 이것은 구현에 달려 있지만 Itanium ABI 사양은 대중적인 선택입니다. gcc와 clang은 (적어도 어느 정도는) 그것을 고수한다.

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

아이테니엄 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); 
} 

클래스의 메모리 레이아웃을 사용하는 소스 파일을 만든 후 clang이 메모리 레이아웃을 표시합니다.

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

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] 

이 연타 기능에 대한 자세한 엘리 Bendersky의 블로그에서 확인할 수 있습니다 :

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

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) 

그것은 멤버 변수를 항목별로하지 않는 (또는 적어도 나는 그것을 얻을하는 방법을 알고하지 않습니다)하지만 당신은 그들이 것이라고 말할 수 있습니다 위의 클래스를 들면, (무엇보다도) 인쇄 clang 레이아웃 에서처럼 오프셋 28에서 64 사이 여야합니다.

하나의 기본 클래스가 primary으로 선택되어 있음을 알 수 있습니다. 따라서 Class에 액세스 할 때 SBase1으로 this 포인터를 조정할 필요가 없습니다.GCC에 대한

등가은 다음과 같습니다

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

비주얼 C++에 대한 것과 동일합니다 :

cl main.cpp /c /d1reportSingleClassLayoutTest_A 

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

관련 문제