Скомпилировать и запустить программу без main() в C

Я пытаюсь скомпилировать и запустить следующую программу без функции main() в C. Я скомпилировал свою программу, используя следующую команду.

gcc -nostartfiles nomain.c

И компилятор дает предупреждение

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400340

Хорошо, проблем нет. то я запускаю исполняемый файл (a.out), оба оператора printf печатаются успешно, а затем получают segmentation fault.

Итак, мой вопрос: Почему ошибка сегментации после успешного выполнения операторов печати?

мой код:

#include <stdio.h>

void nomain()
{
        printf("Hello World...\n");
        printf("Successfully run without main...\n");
}

выход:

Hello World...
Successfully run without main...
Segmentation fault (core dumped)

Примечание:

Здесь флаг -nostartfiles gcc запрещает компилятору использовать стандартные файлы запуска при связывании

Ответ 1

Посмотрим на созданную сборку вашей программы:

.LC0:
        .string "Hello World..."
.LC1:
        .string "Successfully run without main..."
nomain:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        mov     edi, OFFSET FLAT:.LC1
        call    puts
        nop
        pop     rbp
        ret

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

Быстрое решение заключалось бы в вызове exit() в конце вашей программы (и, предположив C11, мы могли бы также отметить функцию как _Noreturn):

#include <stdio.h>
#include <stdlib.h>

_Noreturn void nomain(void)
{
    printf("Hello World...\n");
    printf("Successfully run without main...\n");
    exit(0);
}

Фактически теперь ваша функция ведет себя как обычная функция main, так как после возврата из main функция exit вызывается с возвращаемым значением main.

Ответ 2

В C, когда вызываются функции/подпрограммы, стек заполняется как (в порядке):

  • Аргументы
  • Обратный адрес,
  • Локальные переменные, → верхняя часть стека

main() является начальной точкой, ELF структурирует программу таким образом, что все инструкции сначала будут вставлены сначала, в этом случае printfs.

Теперь программа является усеченной без обратного адреса OR __end__ и infact, она предполагает, что все, что есть в стеке в этом (__end__) месте, является обратным адресом, но, к сожалению, его нет и, следовательно, это аварии.