Множественное наследование, указатели базового класса и sizeof - PullRequest
0 голосов
/ 23 июля 2011

Взять типичная демонстрация того, как значения указателя могут изменяться при приведении :

struct B1 { int x; };
struct B2 { int y; };
struct D  : B1, B2 { };

int main() {
   D d;
   cout << (B1*)&d << " " << (B2*)&d << " " << &d;
}

// Typical output:
// 0xbf814ab4 0xbf814ab8 0xbf814ab4

Я задумался; это смещение, вероятно, не существует, когда B1 не имеет членов, поэтому я проверил и это правда (по крайней мере, в этом случае; не уверен, насколько это поведение гарантировано стандартами):

struct B1 { };
struct B2 { };
struct D  : B1, B2 { };

int main() {
   D d;
   cout << (B1*)&d << " " << (B2*)&d << " " << &d;
}

// Typical output:
// 0xbf6ad95b 0xbf6ad95b 0xbf6ad95b

Но тогда sizeof(T) не может быть 0, поэтому sizeof(B1) по-прежнему 1.

Меня поражает, что это "несоответствие" может потенциально привести к серьезному подверженному ошибкам коду, когда программист предполагает, что (char*)(B2*)&d - (char*)(B1*)&d == (ptrdiff_t)sizeof(B1).

Является ли мой анализ точным?

1 Ответ

4 голосов
/ 23 июля 2011

Объекты B1 и B2 являются подобъектами d.Оператор sizeof предоставляет информацию о размере всего объекта, а не подобъекта.

Стандарт допускает, но не требует, чтобы подобъект базового класса не занимал память.Таким образом, в другой совместимой реализации вы можете обнаружить во втором примере, что подобъекты имеют разные адреса в конце концов.

1.8p5: Если это не битовое поле, то наиболее производный объект должен иметь не- нулевой размер и должен занимать один или несколько байтов памяти.Субъекты базового класса могут иметь нулевой размер.Объект тривиально копируемого или стандартного типа размещения должен занимать смежные байты хранилища.

1.8p6: Если объект не является битовым полем или подобъектом базового класса нулевого размера, адрес этого объекта являетсяадрес первого байта, который он занимает.Два разных объекта, которые не являются ни битовыми полями, ни подобъектами базового класса нулевого размера, должны иметь разные адреса.

И единственное «безопасное» использование арифметики указателей:

  • По указателям на элементы одного и того же массива
  • Гарантия того, что адрес подобъекта y завершенного объекта x находится между &x включительно и &x+1 исключением.

Вычитание двух void* указателей некорректно.Вы, вероятно, имели в виду reinterpret_cast<char*> или что-то.(Еще один признак того, что код очень рискованный.)

5.7p4: Для этих операторов [двоичные + и -] указатель на объект без массива ведет себя так же, какуказатель на первый элемент массива длиной один с типом объекта в качестве типа его элемента.

5.7p6: Когда вычитаются два указателя на элементы одного и того же массива, результатом является разностьиндексы двух элементов массива.... Если оба указателя не указывают на элементы одного и того же объекта массива, поведение не определено.

...