Ответ 1
В коде, который выполняется до main
, есть что-то вроде:
extern "C" int main(int argc, char **argv);
Проблема с вашим кодом заключается в том, что если у вас есть указатель на функцию main
, это не то же самое, что функция (в отличие от Haskell, где функция и указатель funciton в значительной степени взаимозаменяемы - по крайней мере, с мои 0,1% знания Haskell).
Пока компилятор с радостью примет:
int (*func)() = ...;
int x = func();
как действительный вызов указателя функции func
. Однако, когда компилятор генерирует код для вызова func
, он фактически делает это по-другому [хотя стандарт не говорит, как это должно быть сделано, и оно зависит от разных архитектур процессора, на практике оно загружает значение в переменную указателя, а затем вызывает этот контент].
Если у вас есть:
int func() { ... }
int x = func();
вызов func
означает только адрес func
и вызывает это.
Итак, если ваш код действительно компилируется, код запуска до main
будет вызывать адрес вашей переменной main
, а не косвенно читать значение в main
, а затем вызывать это. В современных системах это приведет к segfault, потому что main
живет в сегменте данных, который не является исполняемым, но в более старой ОС он скорее всего сбой из-за main
не содержит реального кода (но он может выполнять несколько инструкций прежде чем он упадет в этом случае - в тусклом и отдаленном прошлом, я случайно запускаю всевозможные "мусор" с довольно трудными для обнаружения причинами...)
Но так как main
является "специальной" функцией, также возможно, что компилятор говорит "Нет, вы не можете этого сделать".
Он работал много лет назад, чтобы сделать это:
char main[] = { 0xXX, 0xYY, 0xZZ ... };
но опять же, это не работает в современной ОС, потому что main
заканчивается в разделе данных и не является исполняемым в этом разделе.
Изменить: после фактического тестирования опубликованного кода, по крайней мере, на моем 64-битном Linux, код действительно компилируется, но сбой, неудивительно, когда он пытается выполнить main.
Запуск в GDB дает следующее:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000600950 in main ()
(gdb) bt
#0 0x0000000000600950 in main ()
(gdb) disass
Dump of assembler code for function main:
=> 0x0000000000600950 <+0>: and %al,0x40(%rip) # 0x600996
0x0000000000600956 <+6>: add %al,(%rax)
End of assembler dump.
(gdb) disass stuff
Dump of assembler code for function stuff():
0x0000000000400520 <+0>: push %rbp
0x0000000000400521 <+1>: mov %rsp,%rbp
0x0000000000400524 <+4>: sub $0x10,%rsp
0x0000000000400528 <+8>: lea 0x400648,%rdi
0x0000000000400530 <+16>: callq 0x400410 <[email protected]>
0x0000000000400535 <+21>: mov $0x0,%ecx
0x000000000040053a <+26>: mov %eax,-0x4(%rbp)
0x000000000040053d <+29>: mov %ecx,%eax
0x000000000040053f <+31>: add $0x10,%rsp
0x0000000000400543 <+35>: pop %rbp
0x0000000000400544 <+36>: retq
End of assembler dump.
(gdb) x main
0x400520 <stuff()>: 0xe5894855
(gdb) p main
$1 = (int (*)(void)) 0x400520 <stuff()>
(gdb)
Итак, мы видим, что main
на самом деле не является функцией, это переменная, которая содержит указатель на stuff
. Код запуска вызывает main
, как если бы он был функцией, но он не смог выполнить там инструкции (поскольку данные и данные имеют бит "без выполнения", а не то, что вы можете видеть это здесь, но я это знаю работает таким образом).
Edit2:
Проверка dmesg
показывает:
a.out [7035]: segfault at 600950 ip 0000000000600950 sp 00007fff4e7cb928 ошибка 15 в a.out [600000 + 1000]
Другими словами, ошибка сегментации происходит немедленно с выполнением main
- потому что она не является исполняемой.
Edit3:
Итак, это немного более сложное, чем это (по крайней мере, в моей библиотеке времени выполнения C), поскольку код, вызывающий main, является функцией, которая переводит указатель на main в качестве аргумента и вызывает его через указатель. Однако это не меняет того факта, что когда компилятор создает код, он создает уровень косвенности меньше, чем нужно, и пытается выполнить переменную с именем main
, а не функцию, на которую указывает переменная.
Листинг __libc_start_main
в GDB:
87 STATIC int
88 LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
89 int argc, char *__unbounded *__unbounded ubp_av,
90 #ifdef LIBC_START_MAIN_AUXVEC_ARG
91 ElfW(auxv_t) *__unbounded auxvec,
92 #endif
В этот момент печать main
дает нам указатель на функцию, указывающий на 0x600950, который является переменной с именем main
(такой же, как и я, описан выше)
(gdb) p main
$1 = (int (*)(int, char **, char **)) 0x600950 <main>
Обратите внимание, что это другая переменная main
, чем та, которая называется main
в источнике, отправленном в вопросе.