C ++ New [] 배열 액세스시 기본 클래스 포인터 충돌
-
06-07-2019 - |
문제
단일 객체를 할당하면이 코드는 제대로 작동합니다. 배열 구문을 추가하려고하면 SEGFAULTS가 있습니다. 왜 이런거야? 여기서 내 목표는 외부 세계에서 클래스 C가 내부적으로 B 객체를 사용하고 있다는 사실을 외부 세계에서 숨기는 것입니다. 프로그램을 게시했습니다 코드 패드 당신이 함께 놀 수 있습니다.
#include <iostream>
using namespace std;
// file 1
class a
{
public:
virtual void m() { }
virtual ~a() { }
};
// file 2
class b : public a
{
int x;
public:
void m() { cout << "b!\n"; }
};
// file 3
class c : public a
{
a *s;
public:
// PROBLEMATIC SECTION
c() { s = new b[10]; } // s = new b;
void m() { for(int i = 0; i < 10; i++) s[i].m(); } // s->m();
~c() { delete[] s; } // delete s;
// END PROBLEMATIC SECTION
};
// file 4
int main(void)
{
c o;
o.m();
return 0;
}
해결책
한 가지 문제는 표현입니다 s[i]
포인터 산술을 사용하여 원하는 객체의 주소를 계산합니다. 부터 s
포인터로 정의됩니다 a
, 결과는 배열에 맞습니다 a
S와 배열의 경우 부정확합니다 b
에스. 상속이 제공하는 동적 바인딩은 메소드, 다른 방법으로 만 작동합니다 (예 : 가상 데이터 구성원 없음, 가상 없음 sizeof
). 따라서 방법을 호출 할 때 s[i].m()
그만큼 this
포인터는 무엇이 될 것인지 설정됩니다 i
th a
배열에서 개체. 그러나 실제로 배열은 하나입니다 b
s, 그것은 객체의 중간 어딘가에 가리키며 (때로는) segfault를 얻습니다 (아마도 프로그램이 객체의 vtable에 액세스하려고 할 때). 가상화 및 과부하로 문제를 해결할 수 있습니다. operator[]()
. (그래도 그것이 실제로 작동하는지 보려고 생각하지 않았습니다.)
또 다른 문제는입니다 delete
비슷한 이유로 소멸자에서. 당신은 그것을 가상화하고 과부하 할 수 있습니다. (다시, 내 머리에 튀어 나온 무작위 아이디어. 작동하지 않을 수도 있습니다.)
물론 캐스팅 (다른 사람들이 제안한대로)도 효과가 있습니다.
다른 팁
10의 배열 생성 b
함께 new
그런 다음 주소를 an에 할당합니다 a*
단지 문제를 요구하고 있습니다.
배열을 다형성으로 처리하지 마십시오.
자세한 내용은 참조하십시오 ARR39-CPP. 배열을 다형성으로 처리하지 마십시오, 섹션에서 06. 배열 및 STL (ARR) 의 CERT C ++ 보안 코딩 표준.
"A"유형이 아닌 유형의 "B"배열이 있으며 A 형의 포인터에 할당합니다. 다형성은 동적 어레이로 전달되지 않습니다.
a* s
a
b* s
그리고 당신은이 시작이 작동하는 것을 볼 것입니다.
아직 바지가 아닙니다 포인터 다형성으로 처리 될 수 있습니다. 그것에 대해 생각하십시오
a* s = new B(); // works
//a* is a holder for an address
a* s = new B[10]
//a* is a holder for an address
//at that address are a contiguos block of 10 B objects like so
// [B0][B2]...[B10] (memory layout)
S를 사용하여 배열을 반복 할 때 사용 된 내용에 대해 생각해보십시오.
s[i]
//s[i] uses the ith B object from memory. Its of type B. It has no polymorphism.
// Thats why you use the . notation to call m() not the -> notation
방금 배열로 변환하기 전에
a* s = new B();
s->m();
s는 주소 일뿐입니다. s [i]와 같은 정적 객체가 아닙니다. 주소 S만이 동적으로 결합 될 수 있습니다. s 란 무엇입니까? 누가 알아? 주소에서 뭔가.
보다 아리C 스타일 배열이 어떻게 배치되는지에 대한 자세한 내용에 대한 자세한 내용은 아래의 훌륭한 답변입니다.
B의 각 인스턴스에는 X 데이터 부재와 "VPTR"(가상 테이블에 대한 포인터)가 모두 포함됩니다.
A의 각 인스턴스에는 "VPTR"만 포함합니다.
따라서 (a)! = 크기의 크기 (b).
이제이 작업을 수행하면 "S = New B [10]RAW (A*의 유형이있는)의 M 메모리 10 인스턴스에 놓여 있습니다.
C :: m () 메소드에서 컴파일러에게 "A"배열을 반복하도록 지시합니다 (s 유형이 있기 때문에), 하지만 , S는 실제로 "B"배열을 가리키고 있습니다. 따라서 컴파일러가 실제로하는 일을 S [i]라고 부를 때 "s + i * sizeof (a)"인데, 컴파일러는 "b"의 단위 대신 "a"단위로 점프하고 a와 b가 없기 때문에 같은 크기는 많은 맘 보주 보를 얻습니다.
나는 당신의 답변을 바탕으로 해결 방법을 알아 냈습니다. 간접 계층을 사용하여 구현 세부 사항을 숨길 수 있습니다. 또한 배열에서 객체를 믹싱하고 일치시킬 수 있습니다. 감사!
#include <iostream>
using namespace std;
// file 1
class a
{
public:
virtual void m() { }
virtual ~a() { }
};
// file 2
class b : public a
{
int x;
public:
void m() { cout << "b!\n"; }
};
// file 3
class c : public a
{
a **s;
public:
// PROBLEMATIC SECTION
c() { s = new a* [10]; for(int i = 0; i < 10; i++) s[i] = new b(); }
void m() { for(int i = 0; i < 10; i++) s[i]->m(); }
~c() { for(int i = 0; i < 10; i++) delete s[i]; delete[] s; }
// END PROBLEMATIC SECTION
};
// file 4
int main(void)
{
c o;
o.m();
return 0;
}