Нужный этаж
Вводная
В данной лабораторной работе нам дан код программы которая вычисляет номер этажа на котором находиться квартира по номеру квартиры и количеству квартир на этаже. Однако код писала пьяная нейросеть и он не работает, что нужно исправить. перед началом работы нужно подготовить директорию с лабой, создав эти файлы:
| Makefile | |
|---|---|
|
в main.c в выделенной строке нужно по аналогии с первой лабой записать свои данные
Этапы выполнения
Первая попытка собрать программу
Если попробовать собрать программу через make то компилятор сразу выдаст нам ошибку:
<логин>@kappa~/inf/task2> make
gcc -g -O0 -c main.c
main.c:9:9: fatal error: studio.h: No such file or directory
9 | #include<studio.h>
| ^~~~~~~~~~
compilation terminated.
make: *** [main.o] Ошибка 1
Чтобы понять в чём ошибка достаточно перевести выделенные строки, компилятор говорит нам что файла studio.h нет. Тут ошибка просто из-за опечатки, мы используем stdio.h, а не studio.h:
| main.c | |
|---|---|
после исправления обязательно копируем ошибку компилятора в файл fixes,log
| fixes.log | |
|---|---|
Вторая попытка
После фиксации ошибки и её исправления снова пытаемся скомпилировать нашу программу:
<логин>@kappa~/inf/task2> make
gcc -g -O0 -c main.c
main.c: In function ‘Main’:
main.c:21:30: error: expected ‘;’ before ‘printf’
21 | scanf("%d", &flat_number)
| ^
| ;
......
24 | printf("Введите число квартир на каждом этаже: ");
| ~~~~~~
make: *** [Makefile:5: main.o] Error 1
здесь компилятор говорит нам, что перед printf он ожидает ;, и более того даже показывает куда её пихнуть, получаем следующее:
| main.c | |
|---|---|
после исправления обязательно копируем ошибку компилятора в файл fixes,log
| fixes.log | |
|---|---|
Третья попытка
Снова пробуем собрать нашу программу:
<логин>@kappa~/inf/task2> make
gcc -g -O0 -c main.c
gcc -g -O0 -o task2 main.o
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x17): undefined reference to `main'
collect2: error: ld returned 1 exit status
make: *** [Makefile:2: task2] Error 1
Компилятор говорит что не может сослаться к main, эта ошибка возникает тогда когда в коде программы нет функции main, которая представляет из себя стартовую точку. В изначальном коде у нас вроде есть функция Main, но язык С регистрочувствительный, и поэтому Main и main это не одно и тоже. Исправляем:
| main.c | |
|---|---|
| fixes.log | |
|---|---|
Четвёртая попытка
Теперь при сборке ошибок не возникает, программа спокойно компилируется, но нам нужно копнуть глубже, для этого нужно при сборке добавить 2 новых флага: -Wall и -Wextra, они проведут более тщательную проверку.
| Makefile | |
|---|---|
Пробуем снова собрать приложение(перед этим выполнив make clean) и получаем следующее:
<логин>@kappa~/inf/task2> make
gcc -g -O0 -c -Wall -Wextra main.c
main.c: In function ‘main’:
main.c:28:12: warning: too many arguments for format [-Wformat-extra-args]
28 | printf("Вам нужно подняться на %d этаж\n",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc -g -O0 -o task2 main.o
Компилятор говорит что в 28 строке в функции printf мы передаём слишком много аргументов. В строке мы вставили %d, то есть сказали что там должно быть целое число, из этого следует что мы должны передать 1 число, но на 29 строке мы видим следующее:
| main.c | |
|---|---|
мы передаём 2 числа, результат деления и номер квартиры. Второе значение лишнее, исправляем:
| fixes.log | |
|---|---|
Пятая попытка
Пробуем снова скомпилировать и в этот раз без ошибок, поэтому нужно включить у компилятора режим душнилы, для этого добавляем флаги -pedantic, pedantic-errors и ansi:
| Makefile | |
|---|---|
пробуем собрать и получаем:
<логин>@kappa~/inf/task2> make
gcc -g -O0 -c -Wall -Wextra -pedantic -pedantic-errors -ansi main.c
main.c: In function ‘main’:
main.c:13:5: error: C++ style comments are not allowed in ISO C90
13 | // Номер квартиры
| ^
main.c:13:5: note: (this will be reported only once per input file)
make: *** [Makefile:5: main.o] Error 1
Теперь эта душнила ругается на то, что в 13 строке мы используем комментарий C++ стиля, что не соответствует стандарту ISO C90, фиксируем это в fixes.log, но не исправляем
| fixes.log | |
|---|---|
Шестая попытка
Теперь мы меняем версию стандарта при проверке с -ansi на -std=c11:
| Makefile | |
|---|---|
после этого приложении собирается без проблем и замечаний, потому что в более современном стандарте такие комментарии уже разрешены
Седьмая попытка
Теперь программа синтаксически написана верно, однако если мы посмотрим на её работу, то обнаружим что иногда она ошибается в подсчётах. всё из-за формулы, мы используем целочисленное деление, которое просто отсекает дробную часть, из-за чего и появляются логические ошибки, чтобы это исправить достаточно изменить формулу на:
| main.c | |
|---|---|
теперь программа работает корректно.
Результат
| fixes.log | |
|---|---|
Возможные вопросы
Сборка
Почему программа с Main() не работает? Почему обязательно нужно писать main() с маленькой буквы?
Ответ
Когда операционная система запускает программу она передаёт управление именно функции main. Язык С регистрочувствительный, поэтому если мы пишем Main, то найти main не удаётся
Зачем мы добавляем флаги -Wall и -Wextra и что нам это даёт?
Ответ
Без этих флагов компилятор будет давать нам информацию только об ошибках, которые мешают компиляции программы. -Wall Включает отображение базового набора предупреждений компилятора, а -Wextra расширяет набор предупреждений. Благодаря этому мы можем увидеть больше сомнительных частей кода и убрать больше багов
Зачем мы добавляем флаги -pedantic, -pedantic-errors и -ansi, что нам это даёт?
Ответ
-pedantic заставляет компилятор строго следовать стандарту языка С, а -pedantic-errors превращает каждое замечание от -pedantic в ошибку компиляции. -ansi задаёт стандарт, а именно стандарт 1989 года(С89)
Почему при смене стандарта -ansi на -std=c11 сообщение компилятора пропало?
Ответ
-ansi это стандарт 1989 года, в котором сказано, что комментарии в коде должны начинаться с /* и заканчиваться */, применение комментариев вида // ... не прописано. Когда мы меняем -ansi на -std=c11 мы меняем стандарт 1989 года на стандарт 2011 года, он более современный и он позволяет нам использовать однострочные комментарии // ...
Работа программы
Почему при делении мы всегда получаем целое число? Как нам получить десятичную дробь в ответе?
Ответ
Тип данных результата при делении определяется типами данных операндов, если у нас оба числа int, то и результат будет int, компилятор строго следует типизации и поэтому дробна часть отсекается. Если нам нужна дробная часть, то хотя бы 1 операнд должен быть либо float, либо double