Первая программа на C и этапы компиляции
Почему C
Операционные системы — Linux, FreeBSD, ядро Windows — в основном написаны на C. Системные вызовы, которые мы будем изучать (fork, mmap, kill, open), описаны как функции C. Экзаменационные задачи тоже приходят в виде кода на C. Поэтому начнём с языка.
C — компилируемый язык низкого уровня: программа переводится прямо в машинный код процессора, а программист сам управляет памятью через указатели.
Первая программа
#include <stdio.h> /* подключаем объявления функций ввода-вывода */
int main(void) /* отсюда начинается выполнение программы */
{
printf("Hello, OS!\n"); /* печать строки; \n — перевод строки */
return 0; /* код завершения: 0 — успех */
}
Разберём построчно:
| Строка | Что значит |
|---|---|
#include <stdio.h> |
Директива препроцессора: вставить сюда содержимое заголовка stdio.h, где объявлена printf |
int main(void) |
Главная функция. int — она возвращает целое число, void — не принимает аргументов |
{ ... } |
Тело функции |
printf(...) |
Вызов функции печати из стандартной библиотеки |
return 0; |
Завершение main с кодом 0 |
Каждая инструкция заканчивается точкой с запятой ;. Комментарии пишутся как /* ... */ или // ....
Код завершения
Число из return в main становится кодом завершения процесса (exit status). По соглашению 0 — успех, всё остальное — ошибка. Родительский процесс получает этот код через системный вызов wait — подробно разберём в модуле о процессах.
$ ./hello
Hello, OS!
$ echo $?
0
Аналогия
Код завершения — как отметка в журнале: процесс уходит и оставляет родителю короткий итог «всё хорошо» (0) или номер проблемы.
Четыре этапа сборки
Команда gcc hello.c -o hello на самом деле запускает цепочку из четырёх программ:
- Препроцессор обрабатывает строки с
#: вставляет файлы#include, подставляет макросы#define, выкидывает комментарии. Результат — чистый текст на C. - Компилятор переводит C в язык ассемблера конкретного процессора.
- Ассемблер превращает ассемблер в машинный код — объектный файл
.o. В нём ещё есть «дыры»: например, адресprintfнеизвестен. - Компоновщик (линкер) соединяет объектные файлы и библиотеки, заполняет адреса функций и создаёт исполняемый файл.
Каждый этап можно увидеть отдельно:
gcc -E hello.c -o hello.i # только препроцессор
gcc -S hello.c -o hello.s # до ассемблера
gcc -c hello.c -o hello.o # до объектного файла
gcc hello.o -o hello # компоновка
Что на самом деле вызывает printf?
printf — функция библиотеки libc. Она форматирует строку в буфер в памяти процесса, а когда буфер заполнится (или встретится \n при выводе в терминал), вызывает системный вызов write(1, буфер, длина). Только write обращается к ядру — оно и выводит символы на терминал. Буферизацию мы ещё вспомним в задаче про fork.
Полезные флаги gcc
| Флаг | Назначение |
|---|---|
-o имя |
Имя выходного файла |
-Wall -Wextra |
Включить предупреждения — всегда используйте |
-g |
Отладочная информация для gdb |
-O2 |
Оптимизация |
-std=c11 |
Стандарт языка |
-lm, -lpthread |
Подключить библиотеку (libm, libpthread) |
Привычка
Компилируйте с -Wall -Wextra: компилятор найдёт половину ошибок до запуска программы.
Итог
- C — компилируемый язык, на котором пишут ядра и системные программы.
- Выполнение начинается с
main; еёreturn— код завершения процесса. - Сборка: препроцессор → компилятор → ассемблер → компоновщик.
printf— библиотечная функция, а работу с устройством выполняет системный вызовwrite.
Практика решено 0 из 5
Расставьте этапы превращения hello.c в исполняемый файл в правильном порядке:
-
Ассемблер: переводит ассемблер в машинный код → объектный файл
hello.o -
Препроцессор: подставляет
#include, раскрывает#define→hello.i -
Компоновщик (линкер): соединяет
hello.oс библиотеками → исполняемый файлhello -
Компилятор: переводит C в ассемблер →
hello.s
Напишите программу, которая печатает строку Hello, OS! и перевод строки,
а затем возвращает операционной системе код успешного завершения 0.
#include <stdio.h>
int main(void)
{
printf("Hello, OS!\n");
return 0;
}
Какой флаг gcc остановит сборку после компиляции и ассемблирования, получив только объектный файл .o?
Программа завершилась return 3; из main. Какое число выведет команда echo $?, выполненная сразу после неё в shell?
Почему ядра ОС (Linux, BSD) и системные утилиты пишут на C?
Войдите, чтобы отмечать пройденные уроки.