Простейшие программы

Пример 1 - Исходный текст и машинный код

Напишем простейшее с точки зрения прикладного программиста приложение, единственной целью которого является его правильный запуск и завершение работы. Для этого в текстовом редакторе vim следует набрать предлагаемый текст программы:

$ vim simple.c

simple.c.png

Замечание 1. В языке Си для введения многострочных комментариев используется пара символов /* и */. Все что заключено между ними является комментарием и в машинный код программы по-умолчанию не включается, так что этот текст используется только программистом и не обязателен для написания.

С учётом этого замечания очевидно, что текст программы состоит всего из одной строки, в которой вызывается главная функция программы main(), имеющая тип void (т.е. ничего не возвращающая операционной системе). Список входных параметров функции тоже пуст. Кроме того, тело главной функции тоже пустое, т.к. блок {} не содержит никаких операторов. Означает ли это, что написанное нами приложение вообще делать не будет? Для ответа на этот вопрос оттранслируем программу с помощью gcc:

$ gcc simple.c

После этого убедимся в том, что в текущем каталоге появился файл с именем a.out.

$ ls -l

Этот файл и есть наша программа. Легко можно убедиться, что она далеко не пуста, а значит какие-то действия выполняет. Давайте запустим её следующим образом:

$ ./a.out

Как и следовало ожидать, программа запустилась и завершилась, не сделав ничего полезного с точки зрения пользователя. Однако с точки зрения системного программиста этот процесс был гораздо более сложным. Сейчас мы попытаемся оценить насколько.

Для начала разберемся с тем, чем же отличаются друг от друга файлы simple.c и a.out. В этом нам поможет команда file. Узнайте её назначение:

$ man file

и запустите её дважды так:

$ file simple.c
$ file a.out

Теперь узнайте назначение команды more:

$ man more

После этого попытаемся изучить наши файлы с её помощью:

$ more simple.c
$ more a.out

Почему последняя введённая нами команда отработала именно так? Очевидно, что для изучения машинных кодов требуются другие средства. Такими средствами являются программы hexdump и objdump:

$ man hexdump
$ man objdump

Теперь с помощью этих инструментов постарайтесь исследовать вашу программу:

$ hexdump -C simple.c | more
$ hexdump -C a.out | more
$ objdump -s simple.c | more
$ objdump -s a.out | more
$ objdump -d a.out | more

Постарайтесь объяснить то, что вы при этом видите на экране.

Пример 2 - Трансляция программ на Си

Процесс трансляции программ, написанных на языке Си, состоит из четырёх шагов: препроцессирования, компиляции, ассемблирования и компоновки. По-умолчанию, все эти шаги выполняются автоматически, однако всегда можно остановить трансляцию вручную после любого этапа. Разберёмся с этим на следующем примере.

Напишите текст следующей программы:

$ vim hello.c

hello.c.png

Теперь изучите какие опции поддерживает транслятор gcc и его версию:

$ gcc --help | more
$ gcc -v

После этого оттранслируйте и запустите вашу программу:

$ gcc hello.c -o hello
$ ls -l
$ ./hello

Объясните, что произошло? Для чего использована опция -o?

Теперь прервём трансляцию после препроцессора:

$ gcc -E hello.c -o hello.1
$ ls -l
$ file hello.1
$ more hello.1

Что содержит файл hello.1? Зачем нужен препроцессор?

Далее остановим трансляцию после компиляции программы:

$ gcc -S hello.c -o hello.2
$ ls -l
$ file hello.2
$ more hello.2

Что представляет собой файл hello.2? Является ли этот файл программой и почему?

Теперь остановим трансляцию после ассемблирования:

$ gcc -c hello.c -o hello.3
$ ls -l
$ file hello.3
$ more hello.3
$ hexdump -C hello.3 | more
$ objdump -s hello.3 | more
$ objdump -d hello.3 | more

Что представляет собой файл hello.3? Можно ли считать, что это готовая программа? Зачем нужна компоновка программы и имеет ли смысл останавливать трансляцию после компоновки?