...
Code Block |
---|
class Base { public: virtual void func(void) { cout << "Base" << endl; } }; class Derived : public Base { public: int i; Derived() { i = 0; } void func(void) { cout << "Derived " << ++i << endl; } }; void walk(class Base *bar, int count) { for (int i = 0; i < count; i++) { bar[i].func(); } } int main(void) { Base dis[53]; Derived dat[53]; walk(dis, 53); walk(dat, 53); // crashes } |
Wiki Markup |
---|
In the last call to {{walk()}}, {{dat\[\]}} is treated as a {{Base\[\]}} and the subscripting no longer works correctly when {{sizeof(Derived) \!= sizeof(Base)}}. This is because {{walk()}} incorrectly believes that the size of each element in {{bar\[\]}} is {{sizeof(Base)}}. To locate the second element in the array (located at {{bar\[1\]}}), {{walk()}} adds the {{sizeof(Base)}} to the address {{bar}}. Assuming the derived object is larger (which is often the case), the resulting pointer refers to a point within the first element and not to the start of the second element located at {{bar + sizeof(Derived)}}. |
Compliant Solution 1
...
Instead of having an array of objects, an array of pointers solves the problem of the objects being of different sizes. With the Base
and Derived
classes as above, we can define the walk
and main
methods as follows.
Code Block |
---|
void walk(class Base *bar [], int count) {
for (int i = 0; i < count; i++) {
(bar[i])->func();
}
}
int main(void) {
Base* dis[3] = {new Base, new Base, new Base};
Base* dat[3] = {new Derived, new Derived, new Derived};
walk(dis, 3);
walk(dat, 3);
}
|
The elements in the arrays are now all the same size (because they are pointers to Base
or Derived
objects) and there is no problem with the array indexing.
Compliant Solution 2
Code Block |
---|
class Base {
public:
virtual void func(void) {
cout << "Base" << endl;
}
};
class Derived : public Base {
public:
int i;
Derived() { i = 0; }
void func(void) {
cout << "Derived " << ++i << endl;
}
};
void walk(class Base *bar, int count) {
for (int i = 0; i < count; i++) {
(bar + i)->func();
}
}
int main(void) {
Base dis[3];
Derived dat[3];
walk(dis, 3);
walk(dat, 3); // crashes
}
|
Wiki Markup |
---|
In the last call to {{walk()}}, {{dat\[\]}} is treated as a {{Base\[\]}} and the pointer arithmetic no longer works correctly when {{sizeof(Derived) \!= sizeof(Base)}}. This is because {{walk()}} incorrectly believes that the size of each element in {{bar\[\]}} is {{sizeof(Base)}}. To locate the second element in the array (located at {{(bar + 1)}}), {{walk()}} adds the {{sizeof(Base)}} to the address {{bar}}. Assuming the derived object is larger (which is often the case), the resulting pointer refers to a point within the first element and not to the start of the second element located at {{bar + sizeof(Derived)}}. |
Compliant Solution 1
Instead of having an array of objects, an array of pointers solves the problem of the objects being of different sizes. With the Base
and Derived
classes as above, we can define the walk
and main
methods as follows.
Code Block |
---|
void walk(class Base *bar [], int count) {
for (int i = 0; i < count; i++) {
(*(bar + i))->func();
}
}
int main(void) {
Base* dis[3] = {new Base, new Base, new Base};
Base* dat[3] = {new Derived, new Derived, new Derived};
walk(dis, 3);
walk(dat, 3);
}
|
The elements in the arrays are now all the same size (because they are pointers to Base
or Derived
objects) and there is no problem with the pointer arithmetic.
Compliant Solution 2
A better approach would be to use vectors and iterators, instead of arrays, as follows. (Note, however, that we have to have vectors of pointers because containers must be homogeneous.)
Code Block |
---|
void walk(vector<Base*>bar) {
for_each (bar.begin(), bar.end(), mem_fun(&Base::func));
}
int main(void) {
vector<Base*> dis(3);
for (int i=0; i<3; i++) dis[i] = new Base;
vector<Base*> dat(3);
for (int i=0; i<3; i++) dat[i] = new Derived;
walk(dis);
walk(dat);
}
|
Consequences
Using arrays polymorphically can result in memory corruption which could lead to an attacker being able to execute arbitrary code.
...