Рассмотрим следующий код:
#include <stdio.h>
void __attribute__ ((constructor)) a_constructor()
{
printf("%s\n", __func__);
}
void __attribute__ ((constructor)) b_constructor()
{
printf("%s\n", __func__);
}
int main()
{
printf("%s\n",__func__);
}
Я компилирую вышеуказанный код как: gcc -ggdb prog2.c -o prog2
. Код работает, как ожидалось.
a_constructor
b_constructor
main
Но когда я вижу его дамп, используя objdump -d prog2 > f
. Существует ни призыв к __do_global_ctors_aux
где - нибудь в _init
или где - либо еще, ни определение __do_global_ctors_aux
. Итак, как вызываются конструкторы? Где определение __do_global_ctors_aux
? Это какая-то оптимизация?
Я также попытался скомпилировать его без такой оптимизации: gcc -ggdb -o0 prog2.c -o prog2
. Просьба уточнить. Компиляция выполняется на 32-битной Linux-машине.
РЕДАКТИРОВАТЬ
Мой вывод из gdb bt:
Breakpoint 1, a_constructor () at prog2.c:5
5 printf("%s\n", __func__);
(gdb) bt
#0 a_constructor () at prog2.c:5
#1 0x080484b2 in __libc_csu_init ()
#2 0xb7e31a1a in __libc_start_main (main=0x8048445 <main>, argc=1, argv=0xbffff014, init=0x8048460 <__libc_csu_init>,
fini=0x80484d0 <__libc_csu_fini>, rtld_fini=0xb7fed180 <_dl_fini>, stack_end=0xbffff00c) at libc-start.c:246
#3 0x08048341 in _start ()
Итак, как вызываются конструкторы?
Если вы посмотрите на разборку, произведенную с помощью gcc -g -o0 -S -fverbose-asm prog2.c -o prog2.s
, существует следующее:
.text
.Ltext0:
.globl a_constructor
.type a_constructor, @function
a_constructor:
.LFB0:
.file 1 "test.c"
.loc 1 4 0
.cfi_startproc
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
.loc 1 5 0
movl $__func__.2199, %edi #,
call puts #
.loc 1 6 0
popq %rbp #
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size a_constructor, .-a_constructor
.section .init_array,"aw"
.align 8
.quad a_constructor
В приведенном выше a_constructor
функция a_constructor
помещается в .text
раздел. А указатель на функцию также добавляется в раздел .init_array
. Перед вызовом main
glibc выполняет итерацию по этому массиву и вызывает все найденные там функции-конструкторы.
Детали специфичны для реализации, и вы не упоминаете о своей реализации.
Совершенно правильная стратегия, используемая некоторыми реализациями, заключается в создании библиотеки времени выполнения, которая содержит реальную точку входа для вашей программы. Эта реальная точка входа сначала вызывает все конструкторы, а затем вызывает main
. Если ваша программа динамически связана и код, стоящий за этой реальной точкой входа, находится в общей библиотеке (например, libc
), тогда четкая разборка вашей программы не может показать вам, где вызван конструктор.
Простой подход для определения того, откуда именно идет вызов, - это загрузить вашу программу в отладчик, установить точку останова на одном из конструкторов и запросить стек вызовов при достижении точки останова. Например, на Cygwin:
$ gdb ./test GNU gdb (GDB) 7.8 Copyright (C) 2014 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-pc-cygwin". Type "show configuration" for configuration details. For bug reporting instructions, please see: . Find the GDB manual and other documentation resources online at: . For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./test...done. (gdb) b a_constructor Breakpoint 1 at 0x4011c6: file test.cc, line 5. (gdb) run Starting program: /home/Harald van Dijk/test [New Thread 4440.0x1734] [New Thread 4440.0xa8c] b_constructor Breakpoint 1, a_constructor () at test.cc:5 5 printf("%s\n", __func__); (gdb) bt #0 a_constructor () at test.cc:5 #1 0x61006986 in __main () from /usr/bin/cygwin1.dll #2 0x004011f6 in main () at test.cc:14 (gdb)
Это показывает, что на Cygwin используется вариант стратегии, о которой я упоминал: реальная точка входа является main
функцией, но компилятор вставляет вызов функции __main
специфичной для Cygwin, в начале, и это функция __main
которая выполняет поиск для всех конструкторов и вызывает их напрямую.
(Кстати, очевидно, что это прерывается, если main
называется рекурсивно: конструкторы будут выполняться во второй раз, поэтому C++ не позволяет main
быть вызванным рекурсивно. C разрешает его, но тогда стандарт C не имеет конструктора функции.)
И вы можете понять, как __main
функция __main
ищет их, не __main
исполняемую программу, а спрашивая компилятор для сгенерированной сборки:
$ gcc -S test.c -o -
Я не буду копировать весь список сборок здесь, но он показывает, что в этой конкретной реализации функции конструктора испускаются в сегменте .ctors
, поэтому было бы легко, чтобы функция __main
просто __main
все функции в этом сегменте без компилятор должен перечислить каждую такую функцию один за другим.