Чтобы найти ошибку в коде нужно просто...
$$$\text{ }$$$Первым делом нужно проверить переполнения типов данных, ошибки вроде =
вместо ==
, то, что вы записываете значение long long
в переменную int
, и многое другое. Предупредить возникновение некоторых глупых ошибок можно включением всех предупреждений компилятора. Экстренно проверить код можно по этой ссылке, там есть примеры. Необходимо заменить код из примера на свой код, дождаться компиляции и пофиксить все проблемные места. Предупреждения не являются ошибками, но могут ими все-таки быть из-за невнимательности.
Описание предупреждений есть тут.
Список использованных предупреждений-Wall -Wextra -pedantic -std=c++17 -O3 -Wshadow -Wformat=2 -Wfloat-equal -Wconversion -Wlogical-op -Wshift-overflow=2 -Wduplicated-cond -Wcast-qual -Wcast-align -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
Затем можно протестировать свое решение при запуске на codeforces. Для этого в меню соревнования нужно нажать Запуск, откроется меню запуска, затем вставить свой код и проверить его на нескольких маленьких тестах при помощи компилятора Clang++17 Diagnostics.
Проверка переполнения#include <bits/stdc++.h>
int main() {
int a, b; std::cin >> a >> b;
std::cout << a * b << std::endl;
}
Для теста 10000000 10000000
вы увидите следующее runtime error: signed integer overflow: 10000000 * 10000000 cannot be represented in type 'int'
Проверка выхода за пределы массива или вектора#include <bits/stdc++.h>
int main() {
int arr[10];
int n; std::cin >> n;
for (int i = 0; i < n; i++) {
std::cin >> arr[i];
}
}
Для теста: 11 0 1 2 3 4 5 6 7 8 9 0
вы увидите следующее ==3752==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x1126fd98 at pc 0x00cb1d97 bp 0x1126fb34 sp 0x1126fb30 WRITE of size 4 at 0x1126fd98 thread T0 #0 0xcb1d96 in std::bas... Ошибка исполнения, код возврата 1
Затем с помощью компилятора GNU G++ можно перевести стандартную библиотеку в режим дебагга следующими макросами:
#define _GLIBCXX_DEBUG 1
#define _GLIBCXX_DEBUG_PEDANTIC 1
#define _FORTIFY_SOURCE 2
И, опять же, позапускать на различных тестах. Смотрите примеры.
Выход за пределы вектора#define _GLIBCXX_DEBUG 1
#define _GLIBCXX_DEBUG_PEDANTIC 1
#define _FORTIFY_SOURCE 2
#include <bits/stdc++.h>
int main() {
std::vector<int> arr(3,1);
std::cout << arr[3];
}
В данном примере, при запуске обнаружится ошибка, и стандартная библиотека нам явно сообщит, что произошло не так:
Error: attempt to subscript container with out-of-bounds index 3, but
container only holds 3 elements.
Ссылка на запуск
Разыменование удаленного итератора#define _GLIBCXX_DEBUG 1
#define _GLIBCXX_DEBUG_PEDANTIC 1
#define _FORTIFY_SOURCE 2
#include <bits/stdc++.h>
int main() {
std::set<int> set{1,2,3,4,5};
auto it = set.begin();
set.erase(set.begin());
std::cout << *it << std::endl;
}
Здесь мы сохраняем итератор, затем удаляем его из сета, а затем попытаемся разыменовать удаленный итератор: возникает ошибка:
Error: attempt to dereference a singular iterator.
Ссылка на запуск
Слияние неотсортированных списков#define _GLIBCXX_DEBUG 1
#define _GLIBCXX_DEBUG_PEDANTIC 1
#define _FORTIFY_SOURCE 2
#include <bits/stdc++.h>
#define all(x) (x).begin(),(x).end()
using vi = std::vector<int>;
int main() {
vi left = {1,2,3};
vi right = {3,1,2};
vi res;
std::merge(all(left), all(right), std::back_inserter(res));
}
Здесь мы используем стандартный алгоритм, который сливает два отсортированных массива в один линейно. Этот алгоритм подразумевает, что оба массива отсортированы, но мы допустили ошибку и не отсортировали второй массив. Увидим следующее:
Error: elements in iterator range [__first2, __last2) are not sorted.
Ссылка на запуск
Неожиданное динамическое расширение вектора#define _GLIBCXX_DEBUG 1
#define _GLIBCXX_DEBUG_PEDANTIC 1
#define _FORTIFY_SOURCE 2
#include <bits/stdc++.h>
#define all(x) (x).begin(),(x).end()
using vi = std::vector<int>;
int main() {
vi arr{1,2,3,4};
auto ptr = arr.begin();
arr.push_back(5);
std::cout << *ptr << std::endl;
}
Сохраняем итератор на начало вектора, а затем добавляем в конец еще один элемент. Происходит ресайз вектора, выделение новой памяти и вся старая память копируется в новое место, и очищается, поэтому итератор начинает указывать на удаленную память, и стандартная библиотека нам об этом сообщает:
Error: attempt to dereference a singular iterator.
Ссылка на запуск
Если вы решаете задачу, попробуйте протестировать самостоятельно на маленьких и средних тестах, возможно, написать более медленное, но точно правильное решение и сравнить на случайных тестах или перебрать все маленькие тесты.
Используйте assert(условие);
для проверки инвариантов. Если условие ложно, то программа завершится с вердиктом "Ошибка исполнения". Это поможет в программах на 50-100 строк проверять самого себя.
Пример использования assert 1#include <iostream>
#include <cassert> // для assert
int main() {
int a, b; std::cin >> a >> b;
// вычисляем разность
int c = a - b;
// проверяем, что c + b == a - самопроверка
assert(c + b == a);
std::cout << c << std::endl;
Пример использования assert 2#include <iostream>
#include <cassert> // для assert
int f(int n) {
// проверяем, что аргумент функции ВСЕГДА НЕОТРИЦАТЕЛЕН:
assert(n >= 0);
if (n == 0) return 1;
return n * f(n - 1);
}
int main() {
int n; std::cin >> n; std::cout << f(n);
Затем идет метод пристального взгляда, когда вы пытаетесь определить, что пошло не так, внимательно читая то, что вы написали, и перепроверяя. Для этих моментов полезно писать простой и легко читаемый код.
Следующие макросы помогут эффективно выводить в консоль переменные с их названиями и значениями, контейнеры вроде std::vector<X>
и std::set<X>
, а также пары значений вида std::pair<X,Y>
.
Рекомендуется прочитать этот блог по поиску ошибок в GCC C++.