Отладка C++: перенос навыка на новый язык

В Python-курсе ты уже учился находить и исправлять чужие ошибки — это отдельный навык, не завязанный на конкретный язык. Сегодня применим его к C++: соберём каталог типичных ошибок этого курса и потренируемся их находить.

Экран с текстом ошибки компилятора, рядом — увеличительное стекло над строкой кода

После урока сможешь: различать ошибку компиляции и ошибку логики, узнавать частые ошибки этого курса, использовать Serial.print как инструмент отладки. В конце — найдёшь и исправишь несколько багов в готовой программе.

Вспомни: в Python-курсе отладка была сквозной линией (Р8, с занятия 3) — искать причину и чинить, не переписывая всё заново. Здесь тот же навык, просто на новом языке: причины ошибок другие, а сам подход «прочитать сообщение → сузить место → проверить гипотезу» — тот же.

Два вида ошибок: компиляция и логика

В C++ программа сначала ПОЛНОСТЬЮ проверяется компилятором — и только если проверка прошла, начинает работать. Ошибка компиляции — программа не соберётся вовсе, симулятор покажет текст ошибки и номер строки ДО запуска. Ошибка логики — программа успешно соберётся и запустится, но поведёт себя не так, как задумано, — тут никакого сообщения не будет, придётся разбираться самому.

  Путь программы Arduino:

  код -> [ КОМПИЛЯЦИЯ ] -> прошивка на плату -> выполнение (setup/loop)
            |
      ошибка здесь = ошибка КОМПИЛЯЦИИ
      (программа вообще не соберётся, покажет текст ошибки)

  Если компиляция прошла, но поведение неверное -
  это ошибка ЛОГИКИ (программа работает, но не так, как задумано)

Частые ошибки компиляции

Пропущенная точка с запятой ;, незакрытая фигурная скобка {, неверный регистр в имени команды (Pinmode вместо pinMode — C++ различает большие и маленькие буквы). Сообщение компилятора обычно указывает номер строки — но настоящая причина иногда стоит СТРОКОЙ ВЫШЕ (например, не закрытая скобка ломает всё, что идёт дальше).

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(9600)
}

Частые ошибки логики — уже встречались на этом курсе

Собери в голове каталог: = вместо == в условии (занятие про кнопку) — присваивание вместо сравнения; неверный порядок условий в if/else if/else (занятие про пороговую логику) — от мягкого к строгому вместо строгого к мягкому; забытое обновление previousMillis внутри if (занятие про millis) — условие остаётся истинным всегда; забытый dht.begin() в setup() (занятие про DHT22) — датчик не запущен, всегда NaN; ошибка на границе цикла — i <= razmer вместо i < razmer (занятие про массивы) — обращение к несуществующему индексу.

Скрытая ловушка: & и && — это РАЗНЫЕ операции

В C++ есть логическое И && (сравнивает истинность условий) и побитовое И & (сравнивает числа побитово, на уровне отдельных битов). Они выглядят похоже, но результат может отличаться даже для положительных чисел.

int temperatura = 4;
int vlazhnost = 8;

Serial.println(temperatura && vlazhnost);
Serial.println(temperatura & vlazhnost);
1
0

temperatura && vlazhnost истинно — оба числа не равны нулю. А temperatura & vlazhnost — это побитовое И самих чисел 4 (0100 в двоичном виде) и 8 (1000): общих единичных битов нет, результат 0. Для условий в if/else всегда используй && и || — одиночные & и | почти всегда не то, что нужно.

Serial.print как инструмент отладки

Когда непонятно, что реально происходит внутри программы, — временно добавь Serial.print в подозрительное место и посмотри РЕАЛЬНОЕ значение переменной, а не то, что кажется по коду. Это универсальный приём, который работает в любом языке — ты уже пользовался им (неявно) в Python-курсе, разбирая вывод программ.

Миссия: найди и почини баги

В программе ниже — три ошибки (одна компиляции, две логики). Вставь код в Wokwi, прочитай сообщение об ошибке, исправь по одной и проверь работу после каждого исправления.

// БАГИ (для проверки себя, не показывай ученику решение раньше времени):
// 1) пропущена ; после pinMode
// 2) previousMillis не обновляется внутри if
// 3) i <= 5 вместо i < 5 в переборе массива размером 5
int pin_led = 13;

void setup() {
  pinMode(pin_led, OUTPUT)
  Serial.begin(9600);
}

unsigned long previousMillis = 0;
const long interval = 500;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    digitalWrite(pin_led, !digitalRead(pin_led));
  }

  float pokazaniya[5] = {22.0, 23.0, 21.0, 24.0, 22.0};
  float summa = 0;
  for (int i = 0; i <= 5; i = i + 1) {
    summa = summa + pokazaniya[i];
  }
}

Проверь себя

Контрольный вопрос. Чем ошибка компиляции отличается от ошибки логики?

АЭто два названия одного и того же типа ошибок
БОшибка компиляции не даёт программе собраться вовсе; ошибка логики — программа работает, но не так, как задумано
ВОшибка компиляции менее серьёзна, чем ошибка логики

Подсказка: Обратись к блоку «Два вида ошибок: компиляция и логика».

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Какая из этих проблем — ошибка КОМПИЛЯЦИИ?

АУсловия if/else if/else проверяются в неверном порядке
БЗабыли обновить previousMillis внутри if
ВПропущена точка с запятой после команды

Подсказка: Ошибка компиляции — та, из-за которой программа вообще не соберётся.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Программа собирается и запускается, но кнопка почему-то всегда «считается нажатой», хотя её не трогали. В условии стоит if (knopka = LOW). Это ошибка компиляции или логики?

АКомпиляции — компилятор обязан был это запретить
БЭто вообще не ошибка, а нормальное поведение
ВЛогики — программа успешно соберётся и запустится, просто поведёт себя неверно

Подсказка: Вспомни занятие про кнопку: = вместо == компилируется без проблем, ломается только поведение.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Что выведет программа?

Исходный код для этого задания:

int a = 10;
int b = a; // должно было быть b = a * 2, но программист ошибся при копировании
Serial.println(b);
А10
Б20
В0

Подсказка: Программа делает ровно то, что написано, а не то, что задумал автор — это и есть логическая ошибка.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. Массив pokazaniya объявлен размером 5. Цикл написан как for (int i = 0; i <= 5; i = i + 1). К какому классу ошибок это относится и что произойдёт?

АОшибка компиляции — программа не соберётся с таким циклом
БЛогическая ошибка на границе — цикл обратится к несуществующему индексу 5
ВНикакой ошибки — массив автоматически расширится до 6 элементов

Подсказка: Вспомни занятие про массивы: индексы массива размером 5 — это 0..4, не 0..5.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. dht.readTemperature() всё время возвращает NaN, хотя в схеме датчик подключён верно. Какую строку скорее всего забыли в setup()?

АpinMode(4, OUTPUT);
БSerial.begin(9600);
Вdht.begin();

Подсказка: Вспомни занятие про DHT22 — там разбирался именно этот симптом.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Задание. Напиши код сам. Ниже — строка с ошибкой компиляции. Перепиши её исправленно (сама ошибка — в блоке «Частые ошибки компиляции» этого занятия).

Исходный код для этого задания:

Serial.begin(9600)

Подсказка: Каждая отдельная команда в C++ обязана заканчиваться символом ;

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. В этом коде переключение светодиода должно происходить раз в interval миллисекунд, но происходит на КАЖДОМ проходе loop() без пауз, как только порог пройден один раз. Какую переменную забыли обновить внутри if?

Исходный код для этого задания:

unsigned long previousMillis = 0;
const long interval = 500;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    digitalWrite(13, !digitalRead(13));
  }
}
Аinterval
БpreviousMillis
ВcurrentMillis

Подсказка: Вспомни занятие про millis(): без обновления этой переменной разность будет расти и всегда останется больше interval.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Задание. Напиши код сам. Перепиши строку с if так, чтобы previousMillis обновлялась внутри if — исправь логическую ошибку из предыдущего задания.

Исходный код для этого задания:

if (currentMillis - previousMillis >= interval) {
  digitalWrite(13, !digitalRead(13));
}

Подсказка: Добавь строку previousMillis = currentMillis; внутрь тела if, до или после переключения светодиода.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Проверить ответ →

Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →

Контрольный вопрос. temperatura равна 4, vlazhnost равна 8 (типы int). Что выведет Serial.println(temperatura & vlazhnost)? Впиши число.

Исходный код для этого задания:

int temperatura = 4;
int vlazhnost = 8;
Serial.println(temperatura & vlazhnost);

Подсказка: & — это побитовое И самих чисел, а не логическое сравнение истинности. Переведи оба числа в двоичный вид и сравни биты.

Онлайн-проверка ответа появится позже

Контрольный вопрос. Скользящее окно объявлено как float okno[5], а RAZMER_OKNA по ошибке отдельно задан как 6. Индекс двигают через indeks = (indeks + 1) % RAZMER_OKNA. К чему это приведёт?

Исходный код для этого задания:

const int RAZMER_OKNA = 6; // ошибка: массив объявлен на 5
float okno[5];
int indeks = 0;

void zapisatPokazanie(float novoe) {
  okno[indeks] = novoe;
  indeks = (indeks + 1) % RAZMER_OKNA;
}
АПрограмма просто будет использовать только 5 из 6 возможных позиций, без проблем
БИндекс сможет дойти до 5 — обращение к okno[5], которого не существует в массиве размером 5
ВКомпилятор не даст скомпилировать такое несовпадение размеров

Подсказка: % RAZMER_OKNA ограничивает индекс диапазоном 0..RAZMER_OKNA-1 — то есть 0..5, а не 0..4, если константы разошлись.

Онлайн-проверка ответа появится позже

Задание. Напиши код сам. Перепиши программу целиком (около 8 строк), исправив ДВЕ ошибки сразу: (1) в условии сравнение написано через = вместо ==, (2) цикл переберает массив размером 4 с условием i <= 4 вместо i < 4.

Исходный код для этого задания:

int knopka = digitalRead(2);
if (knopka = LOW) {
  Serial.println("Нажата");
}

float arr[4] = {1.0, 2.0, 3.0, 4.0};
float summa = 0;
for (int i = 0; i <= 4; i = i + 1) {
  summa = summa + arr[i];
}

Подсказка: Первая ошибка — замени = на == в сравнении. Вторая — замени <= на < в границе цикла.

✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.

Онлайн-проверка ответа появится позже

Что дальше

Теперь ты умеешь находить и различать ошибки компиляции и логики на C++, и у тебя есть каталог типичных багов этого курса. Дальше — итоговый проект: соберём станцию мониторинга целиком, по шагам, за два занятия.

Итог: ты различаешь ошибки компиляции и логики, знаешь частые баги этого курса и умеешь пользоваться Serial.print для отладки. Дальше — итоговый проект: станция мониторинга.

Назад  ·  ↑ В начало урока  ·  ⌂ В начало курса  ·  Вперёд →

Школа Виктора Комлева