Основы программирования игр для Unix-подобных систем. Часть I
Внимание! Этот документ ещё не опубликован.
Автор: Кирилл Полежаев
Предисловие
Данная статья предназначена для программистов игр, которые желают разобраться с основами программирования игр в Unix-подобных операционных системах (в первую очередь систем семейства GNU/Linux/X11, на одной из которых и будут приведены примеры). Так что вам необходим компьютер с Unix или Unix-подобной операционной системой. Вам необходимо убедится, что у вас установлено:
- GCC (коллекция компиляторов GNU);
- драйвера для вашей видеокарты с поддержкой OpenGL;
- библиотеки SDL, SDL_image и проч.;
- devel-пакеты для SDL (заголовочные файлы и проч.);
- man-страницы, для всего и побольше (опционально);
Если вы вдруг встречаете незнакомые термины, сокращения или акронимы, сразу же останавливайтесь и ищите их определение где-нибудь в Wikipedia или в Google.
Работа с компилятором GCC
Если вы вдруг привыкли писать программы примерно по такому сценарию:
- открываем ІDE (интегрированную среду разработчика);
- создаём проект;
- добавляем необходимые файлы;
- редактируем их;
- компилируем;
то возможно, вам необходимо разобраться что же действительно происходит в это время, и как это проделать вручную, без помощи IDE. Это поможет вам понять, зачем вообще нужна IDE и как вручную управлять компиляцией. IDE является очень тяжёлым, не всегда необходимым, и часто абсолютно ненужным в некоторой ситуации инструментом.
Итак. Начнём с простой программы, которую откомпилируем и попытаемся запустить. Я выбираю язык программирования С. Объяснять почему, пожалуй, не буду. Простейшую программу на языке C вы, наверное, уже видели:
int main() { return 0; }
Каждая программа, написанная на языке программирования С должна содержать функцию с именем main, возвращающую значение типа int. В случае успешного, безошибочного завершения работы программы, функция должна вернуть 0. В конце каждого файла последним символом должен быть символ перевода строки. Куда же записать этот код? Не спешите пожалуйста открывать IDE. Давайте для начала просто создадим текстовый файл, содержащий текст программы и более ничего. Как создать файл, содержащий данный текст? А легко, к тому же существует огромное количество способов. Вот наиболее простые для вас:
- C помощью GUI-редактора (редактора с графическим интерфейсом). Если вы делаете графическую игру под Unix-подобную операционную систему, то у вас, очевидно, установлено графическое окружение рабочего стола, основанное на X11 и вы можете создать файл с помощью стандартного текстового редактора вашей системы, например gedit для Gnome или kate для KDE. В таком случае просто запустите редактор, введите код и сохраните файл в директории, в которой вам разрешено это делать.
- C помощью TUI/CLI-редактора (редактора с текстовым интерфейсом / с интерфейсом командной строки). Примером такого редактора может быть GNU Nano. Его можно запустить в одной из консолей (Ctrl+Alt+F1...Ctrl+Alt+F7, или сколько их там у вас) или в эмуляторе терминала, прямо в графической среде. Для запуска Nano необходимо ввести команду
nano
и через пробел указать имя файла (а можно и не указывать, тогда Nano просто запустится). Для выхода из Nano используйте комбинацию клавиш Ctrl+X, а для сохранения результатов труда нажмите Ctrl+O, введите имя файла и нажмите Enter.
- C помощью команды cat. Эта команда склеивает файлы и отправляет их в стандартный поток вывода. Чтобы вывести с её помощью содержимое файла на экран, необходимо ввести
cat
и имя файла через пробел. Для того, чтобы с её помощью создать файл на диске, необходимо ввести её без аргумента, чтобы она посчитала входным потоком текущий стандартный поток ввода (вашу клавиатуру, например) и назначить стандартным потоком вывода для неё файл, в который мы хотим занести текст. Делается это примерно так (сейчас и в будущем я буду обозначать приглашение командной оболочки знаком доллара, который вводить не нужно):
$ cat > main.c
Далее вы с клавиатуры вводите текст. Когда введёте последний символ перевода строки, нажмите Ctrl+Z.
Итак, создайте файл, содержащий код программы и дайте ему вменяемое имя с суффиксом .c. Суффикс .c означает, что этот файл содержит программный код, написанный на языке программирования С. После проделанных действий, я надеюсь, у вас получилась директория содержащая один файл с нашей программой. Я дал файлу имя main.c, вы свободны выбрать любое другое.
Но чтобы запустить программу, этого файла недостаточно. Нам необходимо получить исполняемый файл. Для того, чтобы из исходных кодов получить исполняемый файл, необходимо проделать некоторые действия. Мы воспользуемся компилятором GCC. В простейшем случае нужно ввести комманду
gcc
а в качестве аргументов передать ей имя файла, содержащего исходный код. Делается это примерно так:
$ gcc main.c
В результате в вашей директории должен появится файл с именем
a.out
, который является исполняемым и готовым к запуску. Для того, чтобы его запустить, необходимо ввести его имя, предварив его ./, чтобы указать, что мы запускаем файл из текущей директории а не из стандартных директорий, содержащих исполняемые файлы. Выглядит это так:
$ ./a.out
Если вы ничего не меняли в наших исходных кодах, то после запуска никакого вывода на экране вы не увидите. Давайте чуток поиграемся, чтобы лучше разобраться. Пускай наша программа будет спрашивать у игрока его имя и здороваться с ним, используя полученные сведения. Для этого чуток подкорректируем наш файл:
#include <stdio.h> int main() { char buffer[16]; printf("Введите, пожалуйста, Ваше имя:\n"); scanf("%s", buffer); printf("Здравствуйте, %s.\n", buffer); return 0; }
Вот последовательность действий, которую необходимо выполнить, чтобы из исходных кодов на языке программирования С получить исполняемый файл:
- обработка исходных файлов содержащих программный код на языке С препроцессором;
- компиляция обработанных препроцессором файлов в файлы содержащие код на ассемблере;
- сборка файлов содержащих код на ассемблере в объектные файлы, содержащие код в машинных кодах;
- связывание объектных файлов в один исполняемый файл
Препроцессорная обработка нашего файла будет заключатся лишь в замене директивы
#include <stdio.h>
содержимым файла stdio.h. Вы можете убедится в этом, выполнив следующую команду:
$ gcc -E -o main.i main.c
По умолчанию GCC сразу выполняет все стадии, чтобы получить на выходе исполняемый файл. Аргумент
-E
сообщает ему, что необходимо остановится на стадии препроцессорной обработки и не начинать компиляцию. Аргумент
-o main.i
указывает имя выходного файла. Файлам, содержащим программный код на языке С, не подлежащий препроцессорной обработке, необходимо давать имена с суффиксом .i. Последние аргумент - имя файла, содержащего программный код на языке С, подлежащий процессорной обработке. В результате вы получите файл main.i, содержащий обработанный препроцессором исходный код. Советую вам ознакомится с его содержимым.
Теперь давайте скомпилируем получившийся файл и получим объектный файл. Для этого необходимо ввести:
$ gcc -c -o main.o main.i
Объектный файл содержит имена объектов и машинные инструкции. Исполняемый файл получается из таких файлов (обычно нескольких, возможно даже получившихся в результате компиляции файлов, содержащих код на разных языках программирования) и подключаемых к нему библиотек. Для связывания объектных файлов и получения исполняемого нам необходимо ввести:
$ gcc -o main main.o
В результате наших действий GCC вызывает линковщик, и тот связывает наш файл main.o со стандартной библиотекой С, содержащей необходимые нам функции printf, scanf. А также с некоторыми специальными объектными файлами, содержащими точку входа в программу с кодом, вызывающим функцию main и некоторые другие инструкции. В результате в папке с исходным кодом появился файл с именем main, который можно запустить:
$ ./main Введите, пожалуйста, Ваше имя: Кирилл Здравствуйте, Кирилл.
Заметьте, что если ввести имя, превышающее по длине размер буффера, который мы указали в программе, произойдёт ошибка сегментации:
$ ./main Введите, пожалуйста, Ваше имя: Кирюшычичоночичек Здравствуйте, Кирюшычичоночичек. Segmentation fault (core dumped)
Поэтому всегда будьте осторожны при работе с буфферами. Даже если вы создаёте буффер очень большого размера, заведомо на порядок превышающий по размеру необходимый, взломщик обязательно пришлёт на ваш игровой сервер строку ещё более длинную, чтобы его уронить. А выход за пределы буффера в модуле ядра, например, может вызвать серьёзную ошибку, которая приведёт к прекращению работы операционной системы. В нашей ситуации необходимо указать функции scanf размер буффера. Прочитав пятнадцатый байт, функция прекратит считывание в буффер и таким образом не начнёт запись за его пределами:
$ cat > main.c #include <stdio.h> int main() { char buffer[16]; printf("Введите, пожалуйста, Ваше имя:\n"); scanf("%16s", buffer); printf("Здравствуйте, %s.\n", buffer); return 0; } ^Z [1]+ Stopped cat - > main.c $ gcc -o main main.c $ ./main Введите, пожалуйста, Ваше имя: Кирюшычичоночичек Здравствуйте, Кирюшычи.
Надеюсь, вы поняли, что написано сверху. Но я на всякий случай ещё раз объясню, что значат комманды. Сначала мы вызываем комманду
cat
без аргументов и перенаправляем её вывод в файл main.c с помощью оператора >.
Затем мы вводим текст программы, и не забываем символ конца файла ^Z.
Потом мы компилируем получившийся файл, который содержит исходный код в исполняемый файл:
gcc -o main main.c
.
Запускаем мы его с помощью комманды
./main
.
Следующая часть будет про Make-файлы и SDL.
22 Декабря 2009 - 26 Декабря 2009
#C, #cat, #GCC, #gnu, #Linux, #nano, #sdl, #shell, #Unix, #x11
17 июля 2010 (Обновление: 19 июля 2010)