Я использую Keil 4 с набором инструментов ARM для Cortex M3 (если это даже имеет значение).
Я попробовал этот простой код:
class Base
{
public:
virtual ~Base() {}
};
class Derived : public Base
{
public:
int b;
virtual ~Derived() {}
};
Если я создаю экземпляр Derived локально (внутри main), все в порядке: отладка работает, размер программы составляет около 300 байт.
Если я создаю статический или глобальный экземпляр Derived, размер программы увеличивается до 1000 байт, и сеанс отладки останавливается по инструкции BKPT.
Я разобрался, потому что размер кучи установлен в значение по умолчанию (ноль). Когда я добавил немного кучи, отладка начала работать.
Создание деструктора защищенным, но не виртуальным, привело к тому же поведению. Делать обычный метод виртуальным не стал.
Итак, мой вопрос: по какой причине компилятору нужна куча в этой ситуации?
Vtab создается статически (я проверял), глобальный объект тоже должен быть статическим. Глупо тратить еще 700 байт для кода выделения кучи (и места для самой кучи), когда мне не нужно динамическое распределение.
(Я сделал виртуальный деструктор, чтобы предотвратить предупреждение.)
Когда экземпляр определяется вне функции, происходят две вещи. Сначала конструктор объекта вызывается до вызова main. Программист должен быть осторожным, чтобы конструктор не делал ничего, что могло бы потребовать инициализации, которая произойдет позже. Во-вторых, память для экземпляра выделяется в куче, потому что до main нет локальной области видимости. Программисты встроенных систем должны быть особенно осторожны при создании глобальных объектов. Если конструктор опирается на какое-то оборудование, которое не инициализировано до main, могут произойти плохие вещи. Ознакомьтесь с файлом запуска C, предоставленным Keil (модуль языка ассемблера, который вызывает main после инициализации кучи, копирует flash в ram и вызывает глобальные конструкторы).
Ответ здесь, на форуме Keil — http://www.keil.com/forum/21859/. Причиной является сгенерированная функция «__aeabi_atexit», которая вызывается после возврата из main. Что довольно забавно, потому что этот возврат никогда не произойдет во встроенной системе.