Язык Си для всех

Урок 7. Первая программа

Цель урока: научиться компилировать и запускать программу, закодированную на языке C.

Понятия урока (необходимо выписать из толкового словаря их значения перед продолжением чтения): компилятор, транслятор, компоновщик.

Мы будем учиться писать программы для бесплатной операционной системы (ОС) на основе ядра Linux. Наиболее распространённым её дистрибутивом (пакетом поставляемых программ) является Ubuntu. Для слабых машин подойдёт её разновидность Lubuntu. Чем выше версию ОС Вы сможете установить на Вашем персональном компьютере (ПК), тем более новая версия компилятора будет с ней совместима, а значит, в нём будет больше возможностей для изучения. Кстати, обязательно убедитесь, что Вы устанавливаете стабильную (stable) версию Вашего дистрибутива Linux, иначе Вы можете столкнуться с непредсказуемыми ошибками в её поведении. Подробное описание процедуры установки ОС Вы найдёте, воспользовавшись поисковиком, например, yandex.ru. Для нашего обучения был выбран компилятор GNU Compilers Collection (GCC) со встроенным компоновщиком. Проверить наличие компилятора на Вашей машине можно, запустив приложение терминала Terminal и введя команду поиска расположения:
whereis gcc
Если всё прошло успешно, то Вы увидите набор путей, среди которых будет путь:
/usr/bin/gcc
На машине, где проверялись примеры кода, используется компилятор версии 11.2.0. Проверить версию Вашего компилятора Вы можете командой терминала:
gcc -v
Программисту очень важно научиться пользоваться поисковой машиной интернета, а лучше сразу несколькими, поэтому следующим Вашим заданием будет создание текстового файла (попробуйте узнать о том, как создать и сохранить текстовый файл в Linux, воспользовавшись поисковиком) с названием main.c. Простейшая программа на языке C выглядит так:
int main(){}
Введите её в точности так, как указано в примере, и сохраните файл с исходным кодом Вашей первой программы. Чтобы программа на языке C заработала, недостаточно сохранить её исходный код в текстовом файле, а нужно также её скомпилировать и скомпоновать. Это делается автоматически следующей командой (используйте этот шаблон команды в дальнейшем для всех примеров):
gcc main.c -o run -Ofast -Wall -Wextra -pedantic -std=gnu2x -march=corei7 -mtune=corei7
После ввода этой команды в командную строку терминала и нажатия клавиши Enter ничего выводиться не должно, а вместо этого отобразится приглашение ко вводу следующей команды. Вы можете получить одно или несколько следующих сообщений об ошибке или предупреждений. Не тревожьтесь: исправление ошибок и отладка программ - основная деятельность программиста (кодировщика), опыт в которой ценится в его работе выше всего. Если сообщение приняло вид:
Command 'xx' not found, but can be installed with:
сверьте написание команды gcc с приведённой выше записью. Не пугайтесь, а исправьте написание в соответствии с примером, и перезапустите команду компиляции и компоновки. Чтобы не набирать команду заново, можно воспользоваться историей команд, нажам в терминале клавишу со стрелкой вверх. Если система вывела сообщение:
cc1: fatal error: xxx.x: No such file or directory
compilation terminated.
исправьте написание имени файла с исходным кодом в команде на main.c и перезапустите команду. Если сообщение имеет вид:cc1: error: argument to ‘-O’ should be a non-negative integer, ‘g’, ‘s’ or ‘fast’ исправьте опечатку в записи ключа (параметра команды gcc, начинающегося со знака тире) -Ofast и перезапустите команду. Если Вы получили сообщение:
gcc: error: unrecognized command-line option ‘-xxx’; did you mean ‘-yyy’?
проверьте корректность записи ключей -Wall, -Wextra, -pedantic, -std=gnu2x, -march=corei7, -mtune=corei7 и наличие пробелов между всеми ключами. Если Вы видите следующее сообщение:
main.c:1:1: error: unknown type name ‘xx’; did you mean ‘int’?
с указанием места в строке кода, где это произошло, то в этом месте Вы допустили ошибку в написании слова int - одного из зарезервированных служебных ключевых слов языка программирования C, которые могут быть выделены текстовым редактором особым цветом. Если в процессе компиляции отобразилось сообщение:
main.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
    1 | intmain(){}
      | ^~~~~~~
main.c: In function ‘intmain’:
main.c:1:11: warning: control reaches end of non-void function [-Wreturn-type]
    1 | intmain(){}
      |           ^
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
поставьте пробел между словами int и main и перезапустите команду. Если Вы видите сообщение:
main.c: In function ‘xxx’:
main.c:1:11: warning: control reaches end of non-void function [-Wreturn-type]
    1 | int xxx(){}
      |           ^
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
удостоверьтесь, что слово main записано корректно четырьмя латинскими буквами слитно, и перезапустите команду.
Если же сообщение в консоли вида:
main.c:1:9: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘)’ token
    1 | int main){}
      |         ^
верните на место открывающую круглую скобку, как это показано в коде примера, и перезапустите команду. Если Вы получили сообщение:
main.c:1:10: error: expected declaration specifiers or ‘...’ before ‘{’ token
    1 | int main({}
      |          ^
донаберите единственную закрывающую круглую скобку после открывающей в соответствии с примером кода и перезапустите команду. Если сообщение принимает следующий вид:
main.c: In function ‘main’:
main.c:1:11: error: expected declaration specifiers before ‘}’ token
    1 | int main()}
      |           ^
main.c:3: error: expected ‘{’ at end of input
добавьте единственную открывающую фигурную скобку перед закрывающей, как это показано в примере кода. Если Вы получили сообщение:
main.c: In function ‘main’:
main.c:1:1: error: expected declaration or statement at end of input
    1 | int main(){
      | ^~~
добавьте в конец строки файла исходного кода единственную закрывающую фигурную скобку и перезапустите команду. Если в терминале вывелось сообщение:
main.c:3: warning: ISO C forbids an empty translation unit [-Wpedantic]
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
Вам необходимо удостовериться, что Вы сохранили файл с исходным кодом, а также, что именно в этот файл Вы ввели исходный код из примера. Что делает эта программа: загружается в оперативную память (запускается), возвращает код успешного выполнения 0 операционной системе, выгружается из оперативной памяти (останавливается). Для запуска программы в терминале наберите:
./run
Если вывелось сообщение:
bash: ./xx: No such file or directory
вернитесь к команде компиляции и компоновки стрелкой вверх и исправьте ошибку в слове run, затем перезапустите команду gcc в соответствии с образцом, нажав Enter. Разберём устройство текста программы. Текст состоит из двух слов, разделённых пробелом, и четырёх специальных знаков после них. Всего 12 символов. Слово main всегда присутствует в любой программе на языке C и обозначает точку входа - место текста программы, в котором команды программы начнут исполняться одна за другой после запуска. В языке C точкой входа является функция, и эта функция - единственная в программе функция, имя которой записывается как main, что в переводе с английского означает главная, основная. Остальные функции в программе, если они есть, должны носить другие имена. Слово int (integer в Pascal) обозначает тип данных, которые будут возвращены после завершения выполнения функции main, - в нашем случае возвращается целое число 0, поэтому выбран целочисленный тип данных int. Общепринятым считается правило, когда программы возвращают в ОС код результата выполнения типа int, поэтому функция main всегда должна иметь возвращаемый тип int. Можно явно вернуть 0 из main, что можно записать командой return. Место для ввода пользовательских команд - блок кода, обозначаемый фигурными скобками в конце записи функции main. Чтобы код стал яснее, перепишем функцию так:
int main()
{
// сотрите строку и наберите Ваш код
}
В этой программе добавлен комментарий внутри фигурных скобок. Комментарии исключаются из программы при компиляции и не влияют на размер результирующего исполнимого файла. Здесь комментарий начинается с двух служебных символов, записанных подряд, - прямых косых черт или прямых слэшей, за которыми следует текст комментария. Скомпилируйте и запустите программу командой для gcc, которая использовалась ранее, - этот процесс нужно повторять после каждого изменения кода, чтобы исключить закравшиеся ошибки. Вы увидите, что ничего не изменилось. Сотрём комментарий и запишем команду return:
int main()
{
	return 0;
}
Для этой команды есть равнозначная запись:
int main()
{
	return(0);
}
Здесь 0 - это код результата успешного выполнения программы, который наша программа должна возвращать при штатном завершении, что и происходит без этой команды. Ноль следует за командой return через пробел или сразу - в круглых скобках, что не принципиально для работы программы. В конце команды обязательно ставится знак ; (точка с запятой). Обратите внимание на отступ перед командой return, который задаётся нажатием табулятора (клавиши Tab, генерирующей фиксированное количество пробелов). Скомпилируйте и запустите программу. Ничего не должно поменяться. Если в терминале появилось сообщение наподобие:
main.c: In function ‘main’:
main.c:3:9: error: ‘retun’ undeclared (first use in this function)
    3 |         retun 0;
      |         ^~~~~
main.c:3:9: note: each undeclared identifier is reported only once for each function it appears in
main.c:3:14: error: expected ‘;’ before numeric constant
    3 |         retun 0;
      |              ^~
      |              ;
исправьте опечатки в слове return, проверьте наличие пробела между командой и нулём и повторите процесс. Если сообщение приняло вид:
main.c: In function ‘main’:
main.c:3:17: error: expected ‘;’ before ‘}’ token
    3 |         return 0
      |                 ^
      |                 ;
    4 | }
      | ~  
поставьте знак точку с запятой после 0 и повторите процесс. Вовсе не обязательно оформлять код именно так. Запись могла бы выглядеть иначе:
int main(){return 0;}
хотя предыдущий способ оформления стиля является более распространённым, но важнее, чтобы код было легко изучать, понимать и повторять Вам и Вашей команде. Круглые скобки после main нужны для указания параметров функции, поэтому, если у функции нет параметров, или параметры ей не требуются, правильнее записать так:
int main(void)
{
	return(0);
}
Ключевое слово void в круглых скобках означает отсутствие каких-либо передаваемых параметров и буквально переводится как ничто. Я советую всегда на будущее включать в файл с функцией main команду для сборки программы:
//gcc main.c -o run -Ofast -Wall -Wextra -pedantic -std=gnu2x -march=corei7 -mtune=corei7
int main(void)
{
	return(0);
}
Теперь рассмотрим команду компиляции и компоновки. Часть команды
gcc main.c
означает: найди файл исходного кода на C с точным именем main.c в текущей директории и передай на вход утилите gcc. Имя файла должно оканчиваться на .c, чтобы компилятор автоматически выбрал язык, для исходного текста программы на котором будет осуществлена компиляция. Файл назван main не случайно - именно так называется точка входа программы на языке C, которую он содержит. Рекомендуется придерживаться такого правила: всегда называть файл с функцией main именем main с расширением c - это облегчит его поиск в большом массиве данных в крупных приложениях. Поскольку, как Вы убедились, GCC умеет выдавать разнообразные подсказки при наличии каких-либо ошибок в программе и даже часто может указать место, где нужно внести исправление, разработка больших программ на языке C вполне безопасна. Следующая часть команды:
-o run -Ofast
означает: скомпилируй программу с максимальной оптимизацией по быстродействию и сформируй в результате компоновки файл с точным названием run в текущей директории. И опять, файл не случайно назван говорящим именем run: наличие в дистрибутиве программы такого файла поясняет: начни работу с программой с запуска этого файла. Файл по умолчанию создаётся с разрешением на запуск. Далее идёт часть команды:
-Wall -Wextra
которая говорит GCC о необходимости сгенерировать все возможные предупреждения для этого исходного кода при проведении проверки перед компиляцией. После них следуют ключи:
-pedantic -std=gnu2x
дающие указание GCC проверить исходный код программы на точное соответствие последней версии стандарта языка C ISO C2x с расширениями GNU и вывести необходимые предупреждения. Завершает команду набор ключей
-march=corei7 -mtune=corei7
указывающий на необходимость оптимизации генерируемого машинного кода для конкретного процессора. В данном случае - Intel Core i7. Значения этих двух ключей должны указываться для той модели процессора, на которой предполагается выполнение программы большую часть времени. Компилятор GCC поддерживает широкий набор аппаратных платформ. Конкретные значения ключей march и mtune необходимо уточнить на сайте производителя целевой модели процессора.