Разница между .dtors и atexit () в C ++

В чем разница между функциями в .dtors и функциями, вызываемыми с помощью atexit()?

Насколько я понимаю, функции, отмеченные атрибутом ((destructor)), находятся в сегменте .dtors, и вызываются после выхода. Точно так же функции, добавленные с помощью atexit(fctName), помещаются в массив и также вызываются после нормального завершения выполнения.

Так почему же C ++ предоставляет здесь два разных механизма? Есть ли разные вещи, которые можно сделать только с одним? Могу ли я добавить функцию только динамически, используя atexit()?

Также какие функции вызываются первыми: функции из .dtors или функции, добавленные с помощью atexit()?


person Rafa    schedule 02.02.2015    source источник
comment
C ++ не предоставляет сегмент .dtors. Это деталь реализации. Атрибут destructor также не определен стандартом. Это расширение языка (C). atexit определен в стандарте C, поэтому можно сказать, что он в C ++, потому что он унаследован от C.   -  person eerorika    schedule 02.02.2015


Ответы (3)


Из справочных страниц linux atexit () вызывается в обычном процессе завершение, либо через exit (3), либо через возврат из main () программы.

Что касается .ctors / .dtors, они вызываются, когда разделяемая библиотека, в которой они определены, загружается / выгружается.

Порядок, в котором они будут происходить, совершенно очевиден.

person Tom    schedule 02.02.2015
comment
Что касается разделяемых библиотек ... С точки зрения стандарта, это неопределенное поведение. Но стандарт C ++ требует правильного чередования деструкторов и функций, зарегистрированных с atexit. В прошлом (я не знаю, так ли это до сих пор) в g ++ было несколько ошибок в этом отношении, и он не всегда соответствовал требованиям: см. __cxa_atexit1. - person James Kanze; 02.02.2015
comment
Будет ли поведение другим, если общая библиотека вызовет atexit () с функцией, выполняющей то же самое, что и код в деструкторе? - person Rafa; 02.02.2015
comment
эта запись, кажется, отвечает на ваш вопрос. Похоже, что вне реализации glibc это было бы неопределенным поведением. - person Tom; 03.02.2015
comment
@ user3043261 Любое поведение atexit с разделяемыми библиотеками очень сильно зависит от реализации. Стандарт не распознает разделяемые библиотеки. - person James Kanze; 03.02.2015

В C ++ нет .dtors. Некоторые реализации могут. Это разумный механизм для отслеживания деструкторов глобальных объектов. Насколько я понимаю, это список времени компиляции.

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

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

person MSalters    schedule 02.02.2015
comment
Это не может быть список времени компиляции, поскольку порядок уничтожения должен соответствовать обратному порядку построения и должен правильно чередоваться с вызовами atexit. - person James Kanze; 02.02.2015
comment
@JamesKanze: Я не понимаю, почему это проблема? Список .dtors - это просто скомпилированный список указателей функций, которые динамически относятся к некоторому __atexit__internal списку в качестве соответствующего .ctors завершения. Если есть чередующиеся atexit() вызовы, они тоже будут добавлены в этот __atexit_internal список. Тем не менее, дело в том, что список создается динамически путем объединения записей таблицы и atexit() аргументов. - person MSalters; 02.02.2015
comment
Смотрите мой ответ на ваш комментарий к моему ответу. По сути, вы, кажется, просто рассматриваете простой случай и игнорируете локальную статику и инициализаторы, которые сами вызывают std::atexit. И тот факт, что стандарт требует, чтобы функции, зарегистрированные с помощью atexit, и деструкторы статических объектов располагались в порядке, обратном вызовам atexit и конструкторов. - person James Kanze; 02.02.2015
comment
Да? По-прежнему кажется, что проще начать с начального скомпилированного списка некоторой формы и динамически изменять его, если (и только если) это необходимо. В конце концов, для многих программ вы бы никогда не изменили этот первоначальный список. И когда вы это сделаете, объединение записи в единый связанный список тоже не ракетостроение. - person MSalters; 02.02.2015
comment
Это тоже законное решение, но оно может быть непростым. Как вы определяете, где врезаться? И на практике atexit будет частью библиотеки C и не будет знать об этом списке. Также рассмотрите возможность синглтонов Мейерса, где функция instance имеет локальную статику, будет часто вызываться из конструкторов других статических объектов и чей деструктор также должен быть сращен в нужном месте. - person James Kanze; 02.02.2015
comment
@JamesKanze: вы проходите по списку, используя указатель на каждый узел, увеличивая его каждый раз, когда глобальный ctor успешно завершается. Если вы не закончили список ctor, вы вставляете обработчик atexit в середину списка dtor. После того, как вы закончите создание всех глобальных объектов, указатель окажется в конце списка, поэтому любые будущие atexit вызовы будут добавлять обработчики. Казнь - это движение по списку в обратном порядке. Но действительно, если вам нужно повторно использовать библиотеку C atexit, это не самый удобный способ. - person MSalters; 02.02.2015
comment
Я не говорю, что это невозможно, но поскольку обычно atexit уже существует ... Конечно, проблема усложняется, когда учитываются динамическая загрузка и выгрузка: я предполагаю, что если вы загружаете DLL, зарегистрируйтесь функцию в нем с atexit, затем выгружаете DLL, вы стреляете себе в ногу. И эти реализации не обязательно соблюдают порядок, требуемый стандартом, если вы загружаете и выгружаете библиотеки DLL в произвольном порядке. Поэтому кажется вполне возможным, что реализация использует atexit в двоичном файле, а вашу технику - в библиотеках DLL. - person James Kanze; 03.02.2015

Одна из законных реализаций деструкторов статических объектов - зарегистрировать их с помощью atexit после завершения работы конструктора. Стандарт требует, чтобы порядок был таким же, как если бы использовалась эта реализация. Основное отличие состоит в том, что деструкторы статических объектов являются деструкторами: они будут вызываться автоматически, если объект полностью построен, без какой-либо необходимости с вашей стороны их регистрировать. И у них есть параметр this для доступа к объекту.

РЕДАКТИРОВАТЬ:

Чтобы было ясно: дано

T obj;      //  where obj has static lifetime...

Компилятор сгенерирует функцию:

void __destructObj()
{
    obj.~T();
}

и следующий код инициализации:

new (&obj) T;
std::atexit( __destructObj );

Это независимо от объема obj; один и тот же базовый код работает как для локальной статики, так и для объектов в области пространства имен. (В случае локальных объектов компилятор также должен будет сгенерировать флаг и код, чтобы проверить его, чтобы указать, был ли объект уже инициализирован; ему также необходимо будет предпринять шаги для обеспечения безопасности потоков.)

На самом деле трудно понять, как компилятор мог бы поступить иначе (хотя он мог бы генерировать встроенный код, чтобы делать то, что делает std::atexit), учитывая требования к порядку.

person James Kanze    schedule 02.02.2015
comment
Как бы вы зарегистрировали указатель this для деструктора глобального объекта? За кулисами должен быть какой-то общий механизм (поскольку чередование - это задокументированное поведение), но вы не можете просто повторно использовать общедоступный atexit API. - person MSalters; 02.02.2015
comment
@MSalters Компилятор должен сгенерировать функцию, которая не принимает аргумент, и зарегистрировать ее. Что не так уж сложно, поскольку он знает адрес объекта. С другой стороны, ограничения порядка в стандарте в значительной степени требуют динамической регистрации: если у меня T o1; bool b = (std::atexit( f ), true); T o2;, стандарт требует порядка ~T(o2); f(); ~T(o1);. (Почему другой вопрос. Любой, кто действительно пишет такие вещи, заслуживает расстрела.) - person James Kanze; 02.02.2015
comment
Кажется немного сложным (накладные расходы на эти вспомогательные функции могут быть довольно большими), но это сработает, да. Зачем кому-то это нужно, я думаю, убедительным аргументом была работа Андрея Александреску над правильной очисткой (Phoenix Singleton, IIRC, в Modern C ++ Design). - person MSalters; 02.02.2015
comment
@MSalters Это непросто, но я думаю, что идея в том, что должно быть очень мало объектов со статической продолжительностью хранения. Могу добавить, что ранние компиляторы C ++ этого не делали; они просто сгенерировали единственную функцию, которая вызвала все деструкторы статических объектов (включая, по крайней мере, в одном случае, деструкторы локальной статики, которые никогда не были построены), и вызвала функции, зарегистрированные с atexit либо до любых деструкторов, либо после всех. Стандарт C ++ 98 запрещает такое поведение. - person James Kanze; 02.02.2015