Tutorial :C++ new[] into base class pointer crash on array access



Question:

When I allocate a single object, this code works fine. When I try to add array syntax, it segfaults. Why is this? My goal here is to hide from the outside world the fact that class c is using b objects internally. I have posted the program to codepad for you to play with.

#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;  }  


Solution:1

One problem is that the expression s[i] uses pointer arithmetic to compute the address of the desired object. Since s is defined as pointer to a, the result is correct for an array of as and incorrect for an array of bs. The dynamic binding provided by inheritance only works for methods, nothing else (e.g., no virtual data members, no virtual sizeof). Thus when calling the method s[i].m() the this pointer gets set to what would be the ith a object in the array. But since in actuality the array is one of bs, it ends up (sometimes) pointing to somewhere in the middle of an object and you get a segfault (probably when the program tries to access the object's vtable). You might be able to rectify the problem by virtualizing and overloading operator[](). (I Didn't think it through to see if it will actually work, though.)

Another problem is the delete in the destructor, for similar reasons. You might be able to virtualize and overload it too. (Again, just a random idea that popped into my head. Might not work.)

Of course, casting (as suggested by others) will work too.


Solution:2

Creating an array of 10 b's with new and then assigning its address to an a* is just asking for trouble.

Do not treat arrays polymorphically.

For more information see ARR39-CPP. Do not treat arrays polymorphically, at section 06. Arrays and the STL (ARR) of the CERT C++ Secure Coding Standard.


Solution:3

You have an array of type "b" not of type "a" and you are assigning it to a pointer of type a. Polymorphism doesn't transfer to dynamic arrays.

 a* s  

to a

 b* s  

and you will see this start working.

Only not-yet-bound pointers can be treated polymorphically. Think about it

 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)  

when you iterate over the array using s, think about what is used

 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  

before you converted to an array you just had

 a* s = new B();   s->m();  

s here is just an address, its not a static object like s[i]. Just the address s can still be dynamically bound. What is at s? Who knows? Something at an address s.

See Ari's great answer below for more information about why this also doesn't make sense in terms of how C style arrays are layed out.


Solution:4

Each instance of B contains Both X data member and the "vptr" (pointer to the virtual table).

Each instance of A contain only the "vptr"

Thus , sizeof(a) != sizeof(b).

Now when you do this thing : "S = new b[10]" you lay on the memory 10 instances of b in a raw , S (which has the type of a*) is getting the beginning that raw of data.

in C::m() method , you tell the compiler to iterate over an array of "a" (because s has the type of a*) , BUT , s is actualy pointing to an array of "b". So when you call s[i] what the compiler actualy do is "s + i * sizeof(a)" , the compiler jumps in units of "a" instead of units of "b" and since a and b doesn't have the same size , you get a lot of mambojumbo.


Solution:5

I have figured out a workaround based on your answers. It allows me to hide the implementation specifics using a layer of indirection. It also allows me to mix and match objects in my array. Thanks!

#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;  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »