При каких обстоятельствах указатель vtable может быть нулевым (или 0x1)? - PullRequest
7 голосов
/ 15 января 2010

Я сейчас отлаживаю крашлог.Сбой происходит из-за того, что указатель vtable объекта (c ++ -) равен 0x1, в то время как остальная часть объекта выглядит нормально, насколько я могу судить по журналу аварийного завершения.вызовите виртуальный метод.

Мой вопрос: при каких обстоятельствах указатель vtable может стать нулевым?Оператор delete устанавливает нулевой указатель vtable?

Это происходит в OS X с использованием gcc 4.0.1 (сборка Apple Inc., 5493).

Ответы [ 5 ]

8 голосов
/ 15 января 2010

Любой тип неопределенного поведения , который у вас есть, может привести к этой ситуации. Например:

  • Ошибки в арифметике указателей или другие, которые заставляют вашу программу записывать в недействительную память.
  • Неинициализированные переменные, неверные приведения ...
  • Полиморфная обработка массива может вызвать это как вторичный эффект.
  • Попытка использовать объект после удаления.

См. Также вопросы Какой худший пример неопределенного поведения на самом деле возможен? и Каковы все распространенные неопределенные действия, о которых должен знать программист C ++? .

Лучше всего использовать проверку границ и памяти в качестве помощи при интенсивной отладке.

7 голосов
/ 15 января 2010

Может быть запутаться в памяти - что-то переписывает vtable по ошибке. Существует почти бесконечное количество способов «достичь» этого в C ++. Переполнение буфера, например.

6 голосов
/ 15 января 2010

Очень распространенный случай: попытка вызвать чисто виртуальный метод из конструктора ...

Конструкторы

struct Interface
{
  Interface();
  virtual void logInit() const = 0;
};

struct Concrete: Interface()
{
  virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};

Теперь предположим следующую реализацию Interface()

Interface::Interface() {}

Тогда все в порядке:

Concrete myConcrete;
myConcrete.pure();    // outputs "Concrete"

Это такая боль - вызывать pure после конструктора, было бы лучше разложить на части код, верно?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)

Тогда мы можем сделать это в одну строку !!

Concrete myConcrete;  // CRASHES VIOLENTLY

Почему?

Потому что объект построен снизу вверх. Давайте посмотрим на это.

Инструкция по созданию Concrete класса (примерно)

  1. Выделите достаточно памяти (конечно) и достаточно памяти для _vtable (1 указатель функции на виртуальную функцию, обычно в порядке их объявления, начиная с крайней левой базы)

  2. Вызов Concrete конструктор (код, который вы не видите)

    a> Вызовите Interface конструктор, который инициализирует _vtable с его указателями

    b> Вызовите Interface тело конструктора (вы это написали)

    c> Переопределить указатели в _vtable для этих методов. Переопределение бетона

    d> Вызовите Concrete тело конструктора (вы это написали)

Так в чем проблема? Ну, посмотрите на b> и c> порядок;)

Когда вы вызываете метод virtual из конструктора, он не делает то, на что вы надеялись. Он идет в _vtable для поиска указателя, но _vtable еще не полностью инициализирован. Итак, для всего, что имеет значение, эффект:

D() { this->call(); }

на самом деле:

D() { this->D::call(); }

При вызове виртуального метода из Конструктора вы не получаете полный динамический тип строящегося объекта, у вас есть статический тип текущего конструктора.

В моем примере Interface / Concrete это означает тип Interface, а метод является виртуально чистым, поэтому _vtable не содержит реального указателя (например, 0x0 или 0x01, если ваш компилятор достаточно дружественен) чтобы настроить значения отладки, чтобы помочь вам там.

деструкторы

По совпадению, давайте рассмотрим случай Деструктора;)

struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }

struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }

Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
  std::cout << "Concrete refering to " << m_data << std::endl;
}

Так что же происходит при разрушении? Хорошо, _vtable работает хорошо, и вызывается реальный тип времени выполнения ... что это означает здесь, однако, является неопределенным поведением, потому что кто знает, что случилось с m_data после того, как он был удален и до того, как был вызван деструктор Interface? Не знаю;)

Заключение

Никогда не вызывайте виртуальные методы из конструкторов или деструкторов.

Если это не так, у вас останется повреждение памяти, неудача;)

4 голосов
/ 15 января 2010

Моим первым предположением будет то, что какой-то код memset() использует объект класса.

1 голос
/ 15 января 2010

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

Другие возможности включают в себя перезапись памяти каким-либо указателем - фактически в моем случае это почти всегда так ...

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

...