Малоизвестные полезные параметры компилятора GCC — часть 2
Оригинал: Uncommon but useful GCC command line options — part 2
Автор: Himanshu Arora
Дата публикации: 1 декабря 2016 г.
Перевод: А.Панин
Дата перевода: 8 февраля 2017 г.
Компилятор GCC поддерживает на первый взгляд бесконечное количество параметров командной строки. Конечно же, никто не использует и не изучает все эти параметры в процессе работы над своим программным обеспечением, но существует ряд параметров, о которых должен, а может быть и обязан знать каждый разработчик. Некоторые из этих параметров могут использоваться достаточно часто, другие — от случая к случаю, что, впрочем, не делает их менее полезными.
В рамках данной серии статей мы рассматриваем некоторые из этих малоизвестных, но полезных параметров командной строки компилятора GCC и уже обсудили пару таких параметров в первой статье.
Если вы помните, в начале первой статьи серии я кратко упоминал о том, что параметр -Wall , который обычно используется разработчиками для генерации предупреждений, не позволяет генерировать некоторые специфические предупреждения. Если вы никогда не слышали об этих предупреждениях и методике их активации, вы можете не волноваться, так как мы подробно обсудим их в рамках данной статьи.
Кроме того, мы также обсудим параметры командной строки компилятора GCC для вывода предупреждений, связанных с переменными с плавающей точкой, а также обратим внимание на оптимальную методику работы с большим количеством параметров командной строки компилятора GCC.
Но перед тем, как перейти к рассмотрению обозначенных выше вопросов, следует упомянуть о том, что все примеры, команды и инструкции из данной статьи были протестированы в системе Ubuntu 16.04 LTS с компилятором GCC версии 5.4.0.
Активация вывода предупреждений, не связанных с параметром -Wall
Хотя параметр командной строки -Wall и сообщает компилятору GCC о необходимости вывода подавляющего большинства предупреждений, некоторые предупреждения не выводятся даже при использовании данного параметра. Для их вывода следует использовать параметр -Wextra .
В качестве примера предлагаю рассмотреть следующий код:
#include #include int main() < int i=0; /* . основной код . */ if(i); return 1; return 0; >
Я случайно разместил символ точки с запятой после условной инструкции if . При последующей компиляции кода с помощью приведенной ниже команды компилятор GCC не выведет каких-либо предупреждений:
gcc -Wall test.c -o test
А теперь рассмотрим случай использования параметра -Wextra :
gcc -Wall -Wextra test.c -o test
В этом случае будет выведено аналогичное предупреждение:
test.c: In function ‘main’: test.c:10:8: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body] if(i);
Из приведенного выше текста предупреждения очевидно, что использование параметра командной строки -Wextra привело к активации флага компилятора -Wempty-body , в результате чего был выявлен подозрительный фрагмент кода и выведено соответствующее предупреждение. А это полный список флагов предупреждений, активируемых с помощью рассматриваемого параметра командной строки: -Wclobbered , -Wempty-body , -Wignored-qualifiers , -Wmissing-field-initializers , -Wmissing-parameter-type (только для языка C), -Wold-style-declaration (только для языка C), -Woverride-init , -Wsign-compare , -Wtype-limits , -Wuninitialized , -Wunused-parameter (только при использовании с -Wunused или -Wall ) и -Wunused-but-set-parameter (только при использовании с -Wunused или -Wall ).
Если вас интересуют подробные описания упомянутых флагов, вы можете обратиться к странице руководства компилятора GCC .
Кроме того, параметр командной строки -Wextra позволяет компилятору выводить предупреждения в следующих случаях:
Активация предупреждений, связанных со сравнениями значений с плавающей точкой
Вы можете знать о том, что никогда нельзя осуществлять проверку равенства значений с плавающей точкой (если вы не слышали об этом, вам стоит почитать ответы на часто задаваемые вопросы о сравнении значений с плавающей точкой ). Но если вы случайно осуществите данную операцию, выведет ли компилятор GCC предупреждение или даже сообщение об ошибке? Давайте проверим.
Это код, в котором осуществляется проверка равенства значений переменных с плавающей точкой с помощью оператора == :
#include void compare(float x, float y) < if(x == y) < printf("\n РАВНЫ \n"); >> int main(void)
А это команда компиляции данного кода с помощью компилятора GCC (содержащая как параметр -Wall , так и параметр -Wextra ):
gcc -Wall -Wextra test.c -o test
К сожалению, в процессе исполнения данной команды не будет выведено каких-либо предупреждений, связанных со сравнением значений с плавающей точкой. Быстрый просмотр страницы руководства GCC позволяет обнаружить наличие отдельного параметра командной строки -Wfloat-equal , который должен использоваться в подобных сценариях.
А это команда с данным параметром:
gcc -Wall -Wextra -Wfloat-equal test.c -o test
Данная команда позволяет сгенерировать аналогичный вывод:
test.c: In function ‘compare’: test.c:5:10: warning: comparing floating point with == or != is unsafe [-Wfloat-equal] if(x == y)
Как несложно обнаружить, параметр -Wfloat-equal сообщает компилятору GCC о необходимости генерации предупреждения, связанного со сравнением чисел с плавающей точкой.
А это выдержка из описания данного параметра на странице руководства компилятора GCC :
Данная операция нередко используется по той причине, что программисту удобнее считать значения с плавающей точкой бесконечно точными аппроксимацями дробных чисел. Если вы также прибегаете к ней, вам придется вычислить (путем анализа кода или каким-либо другим образом) максимальную или возможную максимальную погрешность расчета и учесть ее в процессе сравнения значений (а также их вывода, но это уже другая операция). По сути, вместо установления равенства значений вам придется установить, пересекаются ли диапазоны этих двух значений; а для этой цели должны использоваться операторы сравнения, поэтому использование оператора равенства является, скорее всего, плохой практикой.
Оптимальная работа с параметрами командной строки компилятора GCC
Если количество параметров командной строки компилятора GCC в вашем проекте стало настолько большим, что вам неудобно ими управлять, вы можете разместить эти параметры в текстовом файле и передать компилятору имя этого текстового файла в качестве параметра командной строки. Для этой цели должен использоваться специальный параметр командной строки @file .
Например, если ваша команда компиляции выглядит следующим образом:
gcc -Wall -Wextra -Wfloat-equal test.c -o test
То вы можете разместить три связанных с выводом предупреждений параметра в файле с именем, таким, как gcc-options :
$ cat gcc-options -Wall -Wextra -Wfloat-equal
После чего ваша команда компиляции станет более простой и доступной для редактирования:
gcc @gcc-options test.c -o test
А это выдержка из описания параметра @file со страницы руководства компилятора GCC:
Чтение параметров командной строки из файла. Параметры читаются и подставляются вместо параметра @file . Если файла с переданным именем не существует или этот файл не может быть прочитан, параметр будет обработан таким же образом, как и все другие параметры, а не удален.
Параметры в файле должны разделяться с помощью символов пробелов. Символ пробела может быть включен в состав параметра путем помещения этого параметра в одинарные или двойные кавычки. Любой символ (включая обратный слэш) может быть включен в состав параметра путем помещения перед этим символом обратного слэша. Сам файл может содержать дополнительные параметры @file ; все эти параметры будут рекурсивно обрабатываться.
Заключение
На данный момент мы рассмотрели пять малоизвестных полезных параметров компилятора GCC: -save-temps , -g , -Wextra , -Wfloat-equal и @file . Потратьте немного своего времени на их испытание и не забудьте прочитать описание каждого из них на странице руководства компилятора GCC.
Вам знакомы другие подобные параметры командной строки компилятора GCC и вы желаете рассказать о них? Вы можете разместить любую информацию об этих параметрах в разделе комментариев.
gcc -Wall option flag
gcc -Wall включает все предупреждающие сообщения компилятора. Эту опцию следует использовать всегда, чтобы генерировать лучший код.
Синтаксис
$ gcc -Wall [options] [source files] [object files] [-o output file]
пример
Запишите исходный файл myfile.c :
int main()
printf(«Program run!\n»);
int i=10;
>
Обычная сборка myfile.c не выдает сообщений:
$ gcc myfile.c -o myfile
$
Сборка myfile.c с -Wall:
$ gcc -Wall myfile.c -o myfile
myfile.c In function ‘main’:
myfile.c:6:6: warning: unused variable ‘i’
myfile.c:7:1: warning: control reaches end of non-void function
$
Безопасное программирование на Си. Часть 1
В цикле данных статей будет описываться подход к безопасному программированию на Си, будут представлены минимальные сведения об инструментах проверки кода и будут приведены примеры типовых ошибок.
Основная аудитория — студенты первых курсов технических ВУЗов.
Работа с целыми числами
Компилируем правильно
Как делаем обычно
Компиляция программы это процесс получения из исходного кода исполняемого файла.
Пример типовой команды компиляции:
gcc main.c
Где main.c ваш файлик с исходным кодом.
Если вы выполнили эту команду и компилятор ничего Вам не написал, то с небольшой вероятностью банальных ошибок вы избежали. В целом это хорошая история, значит Ваша программа смогла собраться в исполняемый файл с именем по умолчанию a.out .
Но бывают ситуации, когда компилятор во время компиляции может вывести сообщение.
Данное сообщение может быть двух видов:
- Warning (предупреждения) — компилятор предупреждает Вас, что вы скорее всего делаете что-то неправильно (небезопасно), но скомпилирует.
Настоятельно НЕ рекомендуется их игнорировать на первых этапах программирования; - Error (ошибка) (подсвечивается красным) — Вы где-то сильно ошиблись, причем так, что это не дает скомпилировать программу. Скорее всего ошибка кроется в синтаксисе языка.
Как правило при ошибке компилятор в явном виде укажет участок кода, в котором находится ошибка. Тут придется все исправлять, иначе исполняемый файл Вы не получите.
Как нужно делать правильно
Во-первых, компилятор ВАШ ДРУГ. В нем есть встроенный синтаксический и (немного) статический анализаторы кода. Т.е. на этапе компиляции он анализирует исходный код. Если уже на данном этапе была получено предупреждение, то на это стоит обратить внимание.
Но стандартные паттерны определения небезопасных конструкций достаточно ограничены.
Чтобы расширить их добавьте два флага при компиляции
- -Wall (short for «all warnings») включает все стандартные предупреждения компилятора. Этот флаг полезен, чтобы быть уверенным, что все потенциально проблемные участки кода будут выявлены.
Например, флаг -Wall может предупредить о неиспользуемых переменных, неинициализированных переменных, некорректных вызовах функций и так далее. - -Wextra (short for «extra warnings») включает дополнительные предупреждения, кроме тех, которые включает флаг -Wall.
Этот флаг активирует дополнительные предупреждения компилятора, такие как предупреждение о неиспользуемых аргументах функций, предупреждение о несоответствии типов указателей, предупреждение о сравнении разных типов и так далее. - -Wpedantic (short for «pedantic warnings») включает предупреждения связанные с соблюдением стандарта ISO C. Он проверяет форматирование и использование нежелательных раширений этого стандарта.
Данный флаг не влияет на безопасность, но упомянуть о нём стоило.
Т.е. Ваша команда компиляции начинает выглядеть вот так:
gcc -Wall -Wextra main.c
Это приводит к большему выводу ошибок и предупреждений, что хорошо — вы УЖЕ нашли ошибку и можете её исправить, а не попали на неё во время сдачи кода и судорожно пытаетесь исправить.
Пример предупреждения
Пусть у нас есть код простейшей программы:
#include int main()
В данном кусочке кода намеренно допущена ошибка, связанная с типом данных в идентификаторе формата при вызове printf() .
При обычной компиляции командой:
gcc 1.c
Компилятор не выдаст никаких ошибок и предупреждений.
Но стоит добавить пару флагов:
gcc -Wall -Wextra 1.c
Как вывод компилятора меняется:
1.c: In function 'main': 1.c:5:14: warning: format '%f' expects argument of type 'double', but argument 2 has type 'int' [-Wformat=] 5 | printf("%f", a); | ~^ ~ | | | | | int | double | %d
Наглядно видно, что компилятор на пятой строке файла 1.с нашел конструкцию, которая с большой долей вероятности некорректна. Но в тоже время, исполняемый файл был успешно скомпилирован.
Пример ошибки
Воспользуемся примером выше с небольшой доработкой.
#include int main()
Вывод компилятора даже при обычной компиляции подсказывает нам, что на пятой строке ожидался символ точки с запятой.
1.c: In function 'main': 1.c:5:20: error: expected ';' before 'return' 5 | printf("%d", a) | ^ | ; 6 | return 0; | ~~~~~~
Что добавить до идеала
Выше было указано, что нужно исправлять даже предупреждения, поскольку код не такой сложный.
Сделаем это на уровне флага — добавим флаг -Werror . Этот флаг заставляет компилятор воспринимать все предупреждения как ошибки.
Т.е. даже с предупреждением код не скомпилируется.
Итоговая команда компиляции кода:
gcc -Wall -Wextra -Werror main.c
Числа, цифры и операции с ними
В ходе первых лабораторных работ студенты зачастую оперируют только целыми числами.
Основные ошибки при работе с ними — переполнение типа данных и деление на ноль.
Про переполнение разрядной сетки
Пусть у нас имеется знаковое число размером 1 байт (8 бит).
Тогда представление числа 127 будет выглядеть так:
0111 1111
0 — старший бит и он отвечает за знак.
0 — положительное, 1 — отрицательное.
Если сделать 127 + 1, то получится не 128, а -128.
Потому что старший бит отвечает за знак, остальные за само число.:
0111 1111 0000 0001 --------- 1000 0000
Как определить переполнение
Использовать флаг компилятора -ftrapv .
Этот флаг встраивает специальные ассемблерные инструкции в Ваш код, которые проверяют числовые типы данных после арифметических операций.
Если что-то пошло не так, то система завершит Вашу программу с надписью «Abort» (определит переполнение типа данных и не дожидаясь проблем пошлет сигнал SIGABRT).
Пример срабатывания -ftrapv
Имеем следующий код:
#include #include int main()
gcc -ftrapv main.c
В итоге, при запуске вывод будет следующим:
bash: Job 1, './a.out' terminated by signal SIGABRT (Abort)
При перехвате сигнала SIGABRT (Abort) будет создан core файл, содержащий информацию о дампе процесса.
Данный файл можно передать в отладчик и получить необходимую информацию об ошибке.
Но, в явном виде никакой дополнительной диагностической информации выведено не будет. Просто сам факт наличия ошибки.
Как отладить?
Гораздо удобнее использовать санитайзер кода UBSAN.
Undefined Behavior Sanitizer — санитайзер, который определяет неопределенное поведение, например переполнение и деление на ноль. Это инструмент, который встраивается в исполняемый файл на этапе компиляции и при обнаружении ошибки прерывает выполнение программы, а также выдает отладочную информацию.
Важно помнить, что санитайзеры работают в динамике, т.е. чтобы среагировать на ошибку, вы должны выполнить уязвимый участок кода.
Как его использовать?
На этапе компиляции необходимо добавить флаги:
-g — флаг, подключающий отладочную информацию. При срабатывании санитайзера, благодаря данному флагу, в отладочной информации отобразится номер строки исходного кода, на которую сработал санитайзер. Без данного флага вместо номера строки с небезопасной конструкцией будет отображаться её адрес в памяти.
-fsanitize=undefined — флаг санитайзера
Итого, команда компиляции:
clang -g -fsanitize=undefined main.c
С -ftrapv смешивать не стоит.
Для более комфортной работы сменим компилятор на clang. Остальные флаги идентичны флагам gcc.
Пример
Есть следующий код:
#include #include int main()
Компилируем и запускаем:
gcc int_overflow.c ./a.out
Получаем число без ошибок. Компилятор не выдал никаких ошибок при компиляции, работа программы завершилась без ошибок. Т.е. о некорректной работе разработанной программы вы узнаете только по контексту во время эксплуатации.
clang -g -fsanitize=undefined int_overflow.c ./a.out
int_overflow.c:6:28: runtime error: signed integer overflow: 2147483647 * 2147483647 cannot be represented in type 'int' SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior int_overflow.c:6:28 in
int_overflow.c:6:28 — на шестой строке файла int_overflow.c произошло переполнение типа данных int.
Далее описание того, на каких значениях пошли проблемы.
На второй строке вывода — типизация ошибки. В нашем случае это «неопределенное поведение».
Деление на ноль
Как определить? Также с помощью санитайзера UBSAN.
#include int main() < int a = 5; int b = 0; int result = a / b; // деление на ноль printf("result: %d\n", result); return 0; >
clang -g -fsanitize=undefined main.c -o bin_ubsan
С помощью флага -o мы указали имя исполняемого файла, который появится после компиляции bin_ubsan .
Запускаем исполняемый файл и поучаем примерно такой вывод:
div_zero.c:6:20: runtime error: division by zero SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior div_zero.c:6:20 in UndefinedBehaviorSanitizer:DEADLYSIGNAL ==30184==ERROR: UndefinedBehaviorSanitizer: FPE on unknown address 0x55defd77936b (pc 0x55defd77936b bp 0x7ffe5c5ff170 sp 0x7ffe5c5ff150 T30184) #0 0x55defd77936b in main /home/users/klavishnik/2023/sanitizers-examples/ubsan/div_zero/div_zero.c:6:20 #1 0x7f7fed8f720b in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #2 0x7f7fed8f72bb in __libc_start_main@GLIBC_2.2.5 csu/../csu/libc-start.c:381:3 #3 0x55defd74a310 in _start csu/../sysdeps/x86_64/start.S:115 UndefinedBehaviorSanitizer can not provide additional info. SUMMARY: UndefinedBehaviorSanitizer: FPE /home/users/klavishnik/2023/sanitizers-examples/ubsan/div_zero/div_zero.c:6:20 in main ==30184==ABORTING
Не стоит пугаться такого ёмкого вывода.
На что стоит сразу обратить внимание, так это на саму первую строку.
Там дан тип ошибки «division by zero» и на какой строке какой файла эта операция произошла (div_zero.c:6:).
Пожалуй, на этом этапе стоит ввести термин трассы выполнения программы.
По ней может отследить место, на котором произошла ошибка. Она содержит в себе адреса (строки в исходном коде или адреса в исполняемых файлах) вызова функций. Последовательность вызовов и сформирует трассу работы вашей программы. Если вы «поймаете» ошибку, то последний вызов трассы будет указывать на проблемный участок кода.
В данном примере трасса состоит из 4 строк и начинается она снизу (т.е. с строки #3).
Три нижние строки содержат системные вызовы и внутренние функции из glibc, которые нам не интересны. Стоит сразу обратить внимание на строку #0, поскольку именно там начинается работа с собственными файлами.
#0 0x55defd77936b in main /home/users/klavishnik/2023/sanitizers-examples/ubsan/div_zero/div_zero.c:6:20
Здесь присутствуют подробные сведения об ошибке: выведен путь до файла, который содержит ошибку, а также указан номер строки, которая вызвала срабатывание санитайзера.
Переполнение на вводе
У многих возникает вопрос, как отслеживать переполнение на вводе.
Например, когда с помощью scanf() пытаемся записать в int число, большее чем MAX_INT .
Начнем с того, что «переполнение на вводе» это вообще некорректный термин.
Переполнение типа данных может возникнуть только при каких-то манипуляциях над переменными (например, инкрементация числового типа).
Такие переполнения можно ловить с помощью санитайзера UBSAN или флага компиляции -ftrapv .
Как появляется?
При вводе числа большего, чем диапазон выбранного типа данных (например, int ), оно просто откинет лишние биты числа.
Например, целочисленный тип данных int на x86_64 размером 32 бита. Если ввести, например, 33 битное число, старшая часть откинется.
Пример. Есть число:
(dec) 123 456 789 123 (bin) 0001 1100 1011 1110 1001 1001 0001 1010 1000 0011
Это число занимает 5 байт, в int влезет всего 4.
Т.е. старшие 8 бит откидываются и в переменную у нас запишется число:
(bin) 1011 1110 1001 1001 0001 1010 1000 0011 (dec) 1 050 221 187
Как видим, здесь даже не изменился знак числа.
Т.е. отследить это будет крайне сложно.
А как scanf данные получает?
В системе есть несколько потоков — поток ввода ( stdin ), вывода ( stdout ) и ошибок ( stderr ).
Поток ввода берет данные со стандартного устройства ввода (по умолчанию это клавиатура) и хранит его в буфере из которого уже scanf() примет данные.
В таком случае встают два вопроса:
- А что, если мы не смогли считать данные за один раз, значит ли что оставшиеся данные будут храниться в буфере stdin ?
Ответ — да. - Значит ли это, что мы может вызвать функцию scanf() дважды, чтобы она забрала из буфера оставшиеся данные?
Ответ — нет. Второй scanf() заставит дописать данные в буфер ввода, что затрёт старый набор данных, который хранился в нём.
А как избежать потери данных?
Существуют два способа:
- Объявите переменную бОльшего диапазона данных, через которую будете контролировать данные.
Например, для int создайте вторую переменную с типом данных long int и записывайте ввод в неё.
Далее, через обычный if контролируйте введённое число. Если оно больше диапазона int’a — выкидывайте ошибку. Меньше — продолжайте работу.
Этот способ не убережет Вас от ситуации, когда будет введено число, превышающее диапазон long int . - Использовать спецификатор ввода.
Конструкция scanf(«%5i»,&input); считает только 5 символов.
Не забудете написать предупреждение для пользователя.
Релиз — отдельно, тестирование — отдельно
Важно помнить, что добавление специальных флагов компиляции (-ftrapv) или санитайзеров увеличивает размер скомпилированного файла и замедляет скорость его работы из-за выполнения проверок.
Поэтому необходимо делать отдельно Debug и Release сборки.
- Debug-сборка выполняется с включением специальных флагов компиляции / санитайзеров. На ней же выполняются все тесты и проверки.
- Release-сборка компилируется без доп.проверок, и, как правило, включает флаги компиляции, направленные на оптимизацию кода. Она же и передается конечному пользователю для дальнейшего использования.
UPD: Статья была обновлена. Спасибо всем, кто указал на ошибки.
Отдельная благодарность @berez за конструктивную критику и предложения по улучшению статьи.
Gcc wall что это
Рассмотрим создание первой простейшей программы на языке Си с помощью компилятора GCC , который на сегодняшний день является одим из наиболее популярных компиляторов для Cи и который доступен для разных платформ. Более подобному информацию о GCC можно получить на официальном сайте проекта https://gcc.gnu.org/.
Набор компиляторов GCC распространяется в различных версиях. Для Windows одной из наиболее популярных версий является пакет средств для разработки от некоммерческого проекта MSYS2 . Следует отметить, что для MSYS2 требуется 64-битная версия Windows 7 и выше (то есть Vista, XP и более ранние версии не подходят)
Итак, загрузим программу установки MSYS2 с официального сайта MSYS2:

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

На первом шаге установки будет предложено установить каталог для установки. По умолчанию это каталог C:\msys64:

Оставим каталог установки по умолчанию (при желании можно изменить). На следующем шаге устанавливаются настройки для ярлыка для меню Пуск, и затем собственно будет произведена установка. После завершения установки нам отобразить финальное окно, в котором нажмем на кнопку Завершить

После завершения установки запустится консольное приложение MSYS2.exe. Если по каким-то причинам оно не запустилось, то в папке установки C:/msys64 надо найти файл usrt_64.exe :

Теперь нам надо установить собственно набор компиляторов GCC. Для этого введем в этом приложении следующую команду:
pacman -S mingw-w64-ucrt-x86_64-gcc
Для управления пакетами MSYS2 использует пакетный менеджер Packman. И данная команда говорит пакетному менелжеру packman установить пакет mingw-w64-ucrt-x86_64-gcc , который представляет набор компиляторов GCC (название устанавливаемого пакета указывается после параметра -S ).

и после завершения установки мы можем приступать к программированию на языке Си. Если мы откроем каталог установки и зайдем в нем в папку C:\msys64\ucrt64\bin , то найдем там все необходимые файлы компиляторов:

В частности, файл gcc.exe как раз и будет представлять компилятор для языка Си.
Далее для упрощения запуска компилятора мы можем добавить путь к нему в Переменные среды. Для этого можно в окне поиска в Windows ввести «изменение переменных среды текущего пользователя»:

Нам откроется окно Переменныех среды:

И добавим путь к компилятору C:\msys64\ucrt64\bin :

Чтобы убедиться, что набор компиляторов GCC успешно установлен, введем следующую команду:
gcc --version
В этом случае нам должна отобразиться версия компиляторов

Создание первой программы
Итак, компилятор установлен, и теперь мы можем написать первую программу. Для этого потребуется любой текстовый редактор для набора исходного кода. Можно взять распространенный редактор Visual Studio Code или даже обычный встроенный Блокнот.
Итак, создадим на жестком диске папку для исходных файлов. А в этой папке создадим новый файл, который назовем hello.c .

В моем случае файл hello.c находится в папке C:\c.
Теперь определим в файле hello.c простейший код, который будет выводить строку на консоль:
#include // подключаем заголовочный файл stdio.h int main(void) // определяем функцию main < // начало функции printf("Hello METANIT.COM!\n"); // выводим строку на консоль return 0; // выходим из функции >// конец функции
Для вывода строки на консоль необходимо подключить нужный функционал. Для этого в начале файла идет строка
#include
Директива include подключает заголовочный файл stdio.h, который содержит определение функции printf, которая нужна для вывода строки на консоль.
Далее идет определение функции int main(void) . Функция main должна присутствовать в любой программе на Си, с нее собственно и начинается выполнение приложения.
Ключевое слово int в определении функции int main(void) говорит о том, что функция возвращает целое число. А слово void в скобках указывает, что функция не принимает параметров.
Тело функции main заключено в фигурные скобки <>. В теле функции происходит вывод строки на консоль с помощью функции printf, в которую передается выводимая строка «Hello METANIT.COM!».
В конце осуществляем выход из функции с помощью оператора return . Так как функция должна возвращать целое число, то после return указывается число 0. Ноль используется в качестве индикатора успешного завершения программы.
После каждого действия в функции ставятся точка с запятой.

Теперь скомпилируем этот файл. Для этого откроем командную строку Windows и вначале с помощью команды cd перейдем к папке с исходным файлом:
cd C:\c
Чтобы скомпилировать исходный код, необходимо компилятору gcc передать в качестве параметра файл hello.c:
gcc hello.c
После этого будет скомпилирован исполняемый файл, который в Windows по умолчанию называется a.exe . И мы можем обратиться к этому файлу
a.exe
и в этом случае консоль выведет строку «Hello METANIT.COM!», собственно как и прописано в коде.
Стоит отметить, что мы можем переопределить имя компилируемого файла с помощью флага -o и передав ему имя файла, в который будет компилироваться программа. Например:
gcc hello.c -o hello.exe
В этом случае программа будет компилироваться в файл hello.exe , который мы также запустить.

Чтобы не приходилось вводить две команды: одну для компиляции программы и другую для ее запуска, мы можем объединить команды:
gcc hello.c -o hello.exe & hello
Эта команда сначала компилирует код в файл hello.exe, а потом сразу запускает его.