문제

나는 객체를 버퍼에 저장하고 있습니다. 이제 나는 객체의 메모리 레이아웃에 대해 가정 할 수 없다는 것을 알고 있습니다.

객체의 전체 크기를 알고 있다면이 메모리에 대한 포인터를 만들고 호출 함수를 만드는 것이 허용됩니까?

예를 들어 다음 수업이 있다고 말합니다.

[int,int,int,int,char,padding*3bytes,unsigned short int*]

1)이 클래스가 크기 24임을 알고 있고 메모리에서 시작되는 주소를 알고 있다면 메모리 레이아웃을 가정하는 것이 안전하지 않다고 생각합니다. 이 회원들은? (C ++는 어떤 마술에 의해 회원의 올바른 위치를 알고 있습니까?)

2) 이것이 안전하지 않은 경우/OK가 아닌 경우, 모든 인수를 취하고 한 번에 하나씩 버퍼에서 각 인수를 가져 오는 생성자를 사용하는 것 외에 다른 방법이 있습니까?

편집 : 제목이 변경되어 제가 요구하는 것에 더 적합하게 만들 수 있습니다.

도움이 되었습니까?

해결책

모든 멤버를 가져 와서 할당 한 생성자를 작성한 다음 새로 사용하는 것을 사용할 수 있습니다.

class Foo
{
    int a;int b;int c;int d;char e;unsigned short int*f;
public:
    Foo(int A,int B,int C,int D,char E,unsigned short int*F) : a(A), b(B), c(C), d(D), e(E), f(F) {}
};

...
char *buf  = new char[sizeof(Foo)];   //pre-allocated buffer
Foo *f = new (buf) Foo(a,b,c,d,e,f);

이것은 v-table조차도 올바르게 생성 될 것이라는 이점이 있습니다. 그러나 직렬화를 위해 이것을 사용하는 경우, 서명되지 않은 짧은 int 포인터는 어떤 종류의 방법을 사용하여 포인터를 오프셋으로 변환 한 다음 다시 다시 돌아 오지 않는 한, 당신이 그것을 사로화 할 때 유용한 것을 가리키지 않을 것입니다. .

a this 포인터는 정적으로 연결되어 있으며 단순히 기능을 직접 호출합니다. this 명시 적 매개 변수 앞의 첫 번째 매개 변수입니다.

멤버 변수는 오프셋을 사용하여 참조됩니다 this 바늘. 물체가 다음과 같이 배치 된 경우 :

0: vtable
4: a
8: b
12: c
etc...

a Dereferencing으로 액세스 할 수 있습니다 this + 4 bytes.

다른 팁

기본적으로 당신이 제안하는 것은 (희망적으로 무작위가 아님) 바이트를 읽고, 알려진 객체로 캐스팅 한 다음 해당 객체에서 클래스 메소드를 호출하는 것입니다. 바이트가 해당 클래스 방법의 "이"포인터에서 끝날 것이기 때문에 실제로 작동 할 수 있습니다. 그러나 당신은 컴파일 된 코드가 기대하는 곳이 아닌 것들에 대한 진정한 기회를 얻고 있습니다. 그리고 Java 또는 C#과 달리, 이러한 종류의 문제를 포착 할 실제 "런타임"은 없으므로 기껏해야 코어 덤프를 얻을 수 있으며, 더 나빠질수록 메모리가 손상 될 것입니다.

Java의 직렬화/사막화의 C ++ 버전을 원한 것 같습니다. 아마도 그렇게 할 도서관이있을 것입니다.

비 예약 된 기능 호출은 C 함수와 같이 직접 연결됩니다. 객체 (이) 포인터는 첫 번째 인수로 전달됩니다. 함수를 호출하기 위해 객체 레이아웃에 대한 지식이 필요하지 않습니다.

객체 자체를 버퍼에 저장하는 것이 아니라 구성된 데이터를 보관하는 것처럼 들립니다.

이 데이터가 순서대로 메모리에있는 경우 필드는 클래스 내에서 정의됩니다 (플랫폼에 적합한 패딩 포함). 그리고 당신의 유형은 a입니다 현물 상환 지불, 그럼 당신은 할 수 있습니다 memcpy 버퍼에서 유형에 대한 포인터로의 데이터 (또는 캐스트하지만, 다른 유형의 포인터에 대한 캐스트가있는 플랫폼 별 gotchas가 있습니다).

클래스가 포드가 아닌 경우 필드의 메모리 내 레이아웃은 보장되지 않으며 각 재 컴파일에서 변경할 수 있으므로 관찰 된 순서에 의존해서는 안됩니다.

그러나 POD의 데이터로 비 POD를 초기화 할 수 있습니다.

비 빈정 함수가있는 주소에 관한 한 : 컴파일 타임에 정적으로 유형의 모든 인스턴스에 대해 동일한 코드 세그먼트 내 위치에 정적으로 연결됩니다. "런타임"이 포함되어 있지 않습니다. 다음과 같이 코드를 작성할 때 :

class Foo{
   int a;
   int b;

public:
   void DoSomething(int x);
};

void Foo::DoSomething(int x){a = x * 2; b = x + a;}

int main(){
    Foo f;
    f.DoSomething(42);
    return 0;
}

컴파일러는 다음과 같은 작업을 수행하는 코드를 생성합니다.

  1. 기능 main:
    1. 개체에 대한 스택에 8 바이트를 할당하십시오 "f"
    2. 클래스의 기본 초기화기 호출 ""Foo"(이 경우에는 아무것도하지 않습니다)
    3. 인수 값을 푸시하십시오 42 스택에
    4. 포인터를 푸시로 푸시하십시오 "f"스택에
    5. 기능을 호출하십시오 Foo_i_DoSomething@4 (실제 이름은 일반적으로 더 복잡합니다)
    6. 로드 리턴 값 0 축합기 레지스터로
    7. 발신자로 돌아갑니다
  2. 기능 Foo_i_DoSomething@4 (코드 세그먼트의 다른 곳에 위치)
    1. 짐 "x"스택에서 값 (발신자가 푸시 됨)
    2. 2를 곱합니다
    3. 짐 "this"스택의 포인터 (발신자가 푸시 됨)
    4. 필드 오프셋 계산 "a" 이내에 Foo 물체
    5. 계산 된 오프셋을 추가하십시오 this 3 단계에로드 된 포인터
    6. 2 단계에서 계산 된 저장 제품, 5 단계에서 계산 된 오프셋
    7. 짐 "x"스택의 가치
    8. 짐 "this"스택의 포인터
    9. 필드 오프셋 계산 "a" 이내에 Foo 다시 물체
    10. 계산 된 오프셋을 추가하십시오 this 8 단계에로드 된 포인터
    11. 짐 "a"오프셋에 저장된 값,
    12. 추가하다 "a"값,로드 된 int 단계 12로"x"7 단계에로드 된 값
    13. 짐 "this"스택의 포인터
    14. 필드 오프셋 계산 "b" 이내에 Foo 물체
    15. 계산 된 오프셋을 추가하십시오 this 14 단계에로드 된 포인터
    16. 13 단계에서 계산 된 13 단계에서 계산 된 저장 합계
    17. 발신자로 돌아갑니다

다시 말해,이 글을 작성한 것과 같은 코드 (예 : DoSomething 함수의 이름 및 통과 방법과 같은 세부 사항)가 다소 동일한 코드입니다. this 포인터는 컴파일러에 달합니다) :

class Foo{
    int a;
    int b;

    friend void Foo_DoSomething(Foo *f, int x);
};

void Foo_DoSomething(Foo *f, int x){
    f->a = x * 2;
    f->b = x + f->a;
}

int main(){
    Foo f;
    Foo_DoSomething(&f, 42);
    return 0;
}
  1. 이 경우 POD 유형을 가진 객체가 이미 생성되었으며 (새로운 호출 여부에 관계없이 필요한 스토리지를 이미 할당하면 이미 충분합니다) 해당 객체의 함수를 호출하는 것을 포함하여 멤버에 액세스 할 수 있습니다. 그러나 T의 필요한 정렬을 정확하게 알고 T의 크기 (버퍼가 IT보다 작지 않을 수 있음)와 T의 모든 구성원의 정렬을 정확하게 알고있는 경우에만 작동합니다. POD 유형의 경우에도 컴파일러는 다음과 같습니다. 원하는 경우 멤버 사이에 패딩 바이트를 넣을 수 있습니다. 비 POD 유형의 경우 유형에 가상 함수 또는 기본 클래스가없고 사용자 정의 생성자 (물론)가없고 기본 및 모든 비 정적 멤버에도 적용되는 경우 동일한 행운을 가질 수 있습니다.

  2. 다른 모든 유형의 경우 모든 베팅이 꺼져 있습니다. 포드로 먼저 값을 읽은 다음 해당 데이터를 사용하여 POD 유형을 초기화해야합니다.

나는 객체를 버퍼에 저장하고 있습니다. ... 객체의 전체 크기를 알고 있다면이 메모리에 대한 포인터를 만들고 호출 함수를 만들 수 있습니까?

이것은 캐스트를 사용하는 것이 허용되는 정도로 허용됩니다.

#include <iostream>

namespace {
    class A {
        int i;
        int j;
    public:
        int value()
        {
            return i + j;
        }
    };
}

int main()
{
    char buffer[] = { 1, 2 };
    std::cout << reinterpret_cast<A*>(buffer)->value() << '\n';
}

Raw Memory 및 Back Again과 같은 것에 물체를 캐스팅하는 것은 실제로 C 세계에서 매우 일반적입니다. 그러나 클래스 계층 구조를 사용하는 경우 멤버 기능에 대한 포인터를 사용하는 것이 더 합리적입니다.

다음 수업이 있다고 말합니다 : ...

이 클래스가 크기 24라는 것을 알고 있다면 메모리에서 시작되는 주소를 알고 있다면 ...

이것은 상황이 어려워지는 곳입니다. 객체의 크기에는 데이터 구성원의 크기 (및 모든 기본 클래스의 모든 데이터 구성원)와 패딩 및 모든 기능 포인터 또는 구현 의존적 정보 (특정 크기 최적화)에서 저장된 내용 (빈 기본 클래스 최적화)이 포함됩니다. 결과 숫자가 0 바이트 인 경우, 객체는 메모리에서 최소 한 바이트를 가져 가야합니다. 이러한 것들은 대부분의 CPU가 메모리 액세스와 관련하여 언어 문제와 일반적인 요구 사항의 조합입니다. 일을 제대로 작동 시키려고 시도하는 것은 진짜 고통 일 수 있습니다..

객체를 할당하고 원시 메모리에 캐스트하는 경우 이러한 문제를 무시할 수 있습니다. 그러나 객체의 내부를 어떤 종류의 버퍼에 복사하면 머리를 꽤 빨리 뒷받침합니다. 위의 코드는 정렬에 관한 몇 가지 일반적인 규칙에 의존합니다 (즉, 클래스 A가 ints와 동일한 정렬 제한을 가질 것이므로 A에 안전하게 캐스트 될 수 있지만 반드시 보장 할 수는 없습니다. 배열의 일부를 A와 다른 데이터 멤버와 다른 클래스에 부품으로 캐스팅하는 경우 동일합니다).

아, 그리고 객체를 복사 할 때 포인터를 올바르게 처리해야합니다.

당신은 또한 같은 것에 관심이있을 수 있습니다 Google의 프로토콜 버퍼 또는 페이스 북의 중고품.


예, 이러한 문제는 어렵습니다. 그리고 그렇습니다. 일부 프로그래밍 언어는 양탄자 아래에서 그들을 청소합니다. 하지만 깔개 아래에 휩쓸려 많은 물건이 끔찍합니다.:

Sun의 핫스팟 JVM에서 객체 저장소는 가장 가까운 64 비트 경계에 정렬됩니다. 또한 모든 객체에는 메모리에 2 단어 헤더가 있습니다. JVM의 단어 크기는 일반적으로 플랫폼의 기본 포인터 크기입니다. (32 비트 int와 64 비트 이중-96 비트의 데이터로 구성된 객체는 객체 헤더에 대해 두 단어, int의 경우 하나의 단어, 더블의 두 단어는 필요합니다. 그것은 5 단어입니다 : 160 비트. 정렬로 인해이 객체는 192 비트의 메모리를 차지합니다.

이것은 Sun이 메모리 정렬 문제에 대한 비교적 간단한 전술에 의존하고 있기 때문에 (가상 프로세서에서는 모든 메모리 위치에 숯이 존재할 수 있으며, 4 명으로 나눌 수있는 모든 위치에 int가 존재할 수 있으며, 이중은 필요할 수 있습니다. 32로 나눌 수있는 메모리 위치에만 할당되지만 가장 제한적인 정렬 요구 사항은 다른 모든 정렬 요구 사항을 충족하므로 Sun은 가장 제한적인 위치에 따라 모든 것을 정렬합니다).

메모리 정렬에 대한 또 다른 전술은 그 공간의 일부를 되 찾을 수 있습니다..

  1. 클래스에 가상 함수가 포함되어 있지 않은 경우 (따라서 클래스 인스턴스에는 VPTR이 없음) 옳은 클래스 '회원 데이터가 메모리에 배치되는 방식에 대한 가정, 제안하는 일을하는 것이 효과가있을 수 있지만 휴대용이 아닐 수도 있습니다).
  2. 예, 또 다른 방법 (더 관용적이지만 훨씬 안전하지는 않습니다 ... 클래스가 데이터를 어떻게 배치하는지 알아야합니다) 소위 "배치 연산자 신규"와 기본 생성자를 사용하는 것입니다.

그것은 "안전한"이라는 의미에 달려 있습니다. 이런 식으로 메모리 주소를 한 지점으로 시전 할 때마다 컴파일러가 제공하는 유형 안전 기능을 우회하고 자신에게 책임을 져야합니다. Chris에서 알 수 있듯이 메모리 레이아웃 또는 컴파일러 구현 세부 사항에 대해 잘못된 가정을하면 예상치 못한 결과와 느슨한 휴대 성이 나타납니다.

이 프로그래밍 스타일의 "안전성"에 대해 우려하기 때문에 기존 라이브러리와 같은 휴대용 및 유형-안전 방법을 조사하거나 목적을 위해 생성자 또는 할당 연산자를 작성하는 것이 좋습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top