Как виртуальные функции и vtable реализованный?

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

int x;
x = 10;

В этом примере переменная x является int, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.

Но когда вы пытаетесь объявить ссылочный тип, произойдет что-то другое. Возьмите следующий код:

Integer num;
num = new Integer(10);

Первая строка объявляет переменную с именем num, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».

Во второй строке ключевое слово new используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования . (точка).

Exception, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num. Перед созданием объекта вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.

Например, вы можете имеют следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

В этом случае вы не создаете объект obj, скорее предполагая, что он был создан до вызова метода doSomething. К сожалению, этот метод можно вызвать следующим образом:

doSomething(null);

В этом случае obj имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

Альтернативно, там могут быть случаи, когда цель метода заключается не только в том, чтобы работать с переданным в объекте, и поэтому нулевой параметр может быть приемлемым. В этом случае вам нужно будет проверить нулевой параметр и вести себя по-другому. Вы также должны объяснить это в документации. Например, doSomething может быть записано как:

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Наконец, Как определить исключение & amp; причина использования Трассировки стека

105
задан Aquarius_Girl 5 November 2014 в 05:44
поделиться

7 ответов

Как виртуальные функции реализованы на глубоком уровне?

От "Виртуальные функции в C++" :

Каждый раз, когда программе объявили виртуальную функцию, v - таблица создается для класса. V-таблица состоит из адресов к виртуальным функциям для классов, которые содержат одну или несколько виртуальных функций. Объект класса, содержащего виртуальную функцию, содержит виртуальный указатель, который указывает на базовый адрес виртуальной таблицы в памяти. Каждый раз, когда существует вызов виртуальной функции, v-таблица используется для разрешения к функциональному адресу. Объект класса, который содержит одну или несколько виртуальных функций, содержит виртуальный указатель, названный vptr в самом начале объекта в памяти. Следовательно размер объекта в этом случае увеличивается размером указателя. Этот vptr содержит базовый адрес виртуальной таблицы в памяти. Обратите внимание, что виртуальные таблицы являются конкретным классом, т.е. существует только одна виртуальная таблица для класса независимо от количества виртуальных функций, которые это содержит. Эта виртуальная таблица в свою очередь содержит базовые адреса одной или нескольких виртуальных функций класса. В то время, когда виртуальная функция вызвана на объекте, vptr того объекта обеспечивает базовый адрес виртуальной таблицы для того класса в памяти. Эта таблица используется для разрешения вызова функции, поскольку это содержит адреса всех виртуальных функций того класса. Это - то, как динамическое связывание разрешено во время вызова виртуальной функции.

vtable может быть изменено или даже непосредственно получено доступ во времени выполнения?

Универсально, я полагаю, что ответ является "нет". Вы могли сделать некоторое искажение памяти для нахождения vtable, но Вы все еще не будете знать то, на что функциональная подпись похожа для вызова его. Что-либо, чего Вы хотели бы достигнуть с этой способностью (который поддерживает язык) должно быть возможным без доступа к vtable непосредственно или изменению его во времени выполнения. Также отметьте, спецификация языка C++ не делает , определяют, что vtables требуются - однако, именно так большинство компиляторов реализует виртуальные функции.

vtable существует для всех объектов, или только тех, которые имеют по крайней мере одну виртуальную функцию?

я верю , ответ здесь, "он зависит от реализации", так как спецификация не требует vtables во-первых. Однако на практике я полагаю, что все современные компиляторы только создают vtable, если класс имеет по крайней мере 1 виртуальную функцию. Существует пространство, наверху связанное с vtable и время, наверху связанное с вызыванием виртуальной функции по сравнению с невиртуальной функцией.

абстрактные классы просто имеют ПУСТОЙ УКАЗАТЕЛЬ для указателя функции по крайней мере одной записи?

ответ - он, является неуказанным спецификацией языка, таким образом, это зависит от реализации. Вызов чистой виртуальной функции приводит к неопределенному поведению, если это не определяется (который это обычно не) (ISO/IEC 14882:2003 10.4-2). На практике это действительно выделяет слот в vtable для функции, но не присваивает адрес ему. Это оставляет vtable неполное, которое требует, чтобы производные классы реализовали функцию и завершили vtable. Некоторые реализации действительно просто помещают Нулевого указателя в vtable запись; другие реализации помещают указатель на фиктивный метод, который делает что-то подобное утверждению.

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

Делает наличие единственной виртуальной функции, замедляют целый класс или только вызов к функции, которая является виртуальной?

Это добирается до края моего знания, таким образом, кто-то, помогите мне здесь, если я неправ!

я полагаю , что только функции, которые являются виртуальными в классе, испытывают хит производительности времени, связанный с вызыванием виртуальной функции по сравнению с невиртуальной функцией. Пространство наверху для класса там так или иначе. Обратите внимание, что, если существует vtable, существует только 1 на класс , не один на [1 110] объект .

скорость становится затронутой, если виртуальная функция на самом деле переопределяется или нет, или это не имеет никакого эффекта, пока это является виртуальным?

я не верю времени выполнения виртуальной функции, которая переопределяется уменьшения по сравнению с вызыванием основной виртуальной функции. Однако существует дополнительное пространство наверху для класса, связанного с определением другого vtable для производного класса по сравнению с базовым классом.

Дополнительные Ресурсы:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (через путь назад машина)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

119
ответ дан 10 revs, 4 users 87% 24 November 2019 в 04:00
поделиться
  • vtable может быть изменено или даже непосредственно получено доступ во времени выполнения?

Не портативно, но если Вы не возражаете против злых шуток, уверенных!

ПРЕДУПРЕЖДЕНИЕ : Эта техника не рекомендуется для использования детьми, взрослыми моложе 969 , или маленькие пушистые создания от Alpha Centauri. Побочные эффекты могут включать демоны, которые летят из Вашего носа , резкое появление Yog-Sothoth, поскольку необходимое утверждающее лицо на всем последующем коде рассматривает, или добавление обратной силы IHuman::PlayPiano() ко всем существующим экземплярам]

В большинстве компиляторов, я видел, vtbl * составляет первые 4 байта объекта, и vtbl содержание является просто массивом членских указателей там (обычно в порядке, которым они были объявлены с первым базовым классом). Существуют, конечно, другие возможные разметки, но это - то, что я обычно наблюдал.

class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

Теперь для получения по запросу некоторых интриг...

Изменяющийся класс во времени выполнения:

std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

Замена метода для всех экземпляров (monkeypatching класс)

Это немного более хитрое, так как сам vtbl находится, вероятно, в постоянной памяти.

int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

последний, довольно вероятно, составит антивирусные проверки, и ссылка просыпаются и замечают, из-за mprotect манипуляций. В процессе с помощью NX укусил его, может перестать работать.

31
ответ дан Ondrej Slinták 24 November 2019 в 04:00
поделиться

Этот ответ был включен в Сообщество ответ Wiki

  • , абстрактные классы просто имеют ПУСТОЙ УКАЗАТЕЛЬ для указателя функции по крайней мере одной записи?

ответ для этого - то, что это является неуказанным - вызов чистых результатов виртуальной функции в неопределенном поведении, если это не определяется (который это обычно не) (ISO/IEC 14882:2003 10.4-2). Некоторые реализации действительно просто помещают Нулевого указателя в vtable запись; другие реализации помещают указатель на фиктивный метод, который делает что-то подобное утверждению.

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

2
ответ дан Community 24 November 2019 в 04:00
поделиться

Можно воссоздать функциональность виртуальных функций в C++ с помощью указателей функции в качестве членов класса и статических функций как реализации, или с помощью указателя на функции членства и функции членства для реализаций. Существуют только письменные преимущества между этими двумя методами... на самом деле, вызовы виртуальной функции являются просто письменным удобством сами. На самом деле наследование является просто письменным удобством..., оно может все быть реализовано, не используя функции языка для наследования.:)

ниже непротестированное дерьмо, вероятно, содержащий ошибки код, но надо надеяться демонстрирует идею.

, например,

class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};
2
ответ дан jheriko 24 November 2019 в 04:00
поделиться

Каждый объект имеет vtable указатель, который указывает на массив функций членства.

1
ответ дан 24 November 2019 в 04:00
поделиться

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

абстрактные классы просто имеют ПУСТОЙ УКАЗАТЕЛЬ для указателя функции по крайней мере одной записи?

ответ - то, что никакая виртуальная таблица не составлена вообще для абстрактных классов. Нет никакой потребности, так как никакие объекты этих классов не могут быть созданы!

, Другими словами, если мы имеем:

class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

vtbl указатель, к которому получают доступ через свинец, будет vtbl класса D. Это точно, как полиморфизм реализован. Таким образом, как к методам D получают доступ через свинец нет никакой потребности в vtbl для класса B.

В ответ на комментарий Mike's ниже...

, Если класс B в моем описании имеет виртуальный метод нечто () , который не переопределяется D и виртуальным методом панель () , который переопределяется, тогда D vtbl будет иметь указатель на B нечто () и на ее собственное панель () . Нет все еще никакого vtbl, созданного для B.

0
ответ дан Andrew Stein 24 November 2019 в 04:00
поделиться

Обычно с VTable, массивом указателей на функции.

2
ответ дан Lou Franco 24 November 2019 в 04:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: