Чи є якесь правило, якого ми не дотримувались?
Так, ви не змогли його правильно перевірити. Ви не самотні, і ви в потрібному місці для навчання :)
C ++ має багато не визначеної поведінки, не визначена поведінка проявляється в тонких і дратівливих способах.
Ви, ймовірно, ніколи не можете написати 100% безпечний код C ++, але ви, безумовно, можете зменшити ймовірність випадкового введення Невизначеної поведінки у свою кодову базу, використовуючи ряд інструментів.
- Попередження компілятора
- Статичний аналіз (розширена версія попереджень)
- Інструментальні тестові бінарні файли
- Загартовані виробничі бінарні товари
У вашому випадку я сумніваюся, що (1) та (2) допомогли б дуже багато, хоча загалом я раджу їх використовувати. А тепер давайте зосередимось на двох інших.
І gcc, і Clang містять -fsanitize
прапор, який інструмент програм, які ви збираєте, щоб перевірити наявність різноманітних проблем. -fsanitize=undefined
наприклад, буде ловити підписаний цілий цілий перелив / переповнення, зміщення на занадто велику кількість тощо ... У вашому конкретному випадку, -fsanitize=address
і -fsanitize=memory
, швидше за все, виникне проблема ... за умови, що у вас є тест виклику функції. Для повноти -fsanitize=thread
варто використовувати, якщо у вас є багатопотокова база даних коду. Якщо ви не можете реалізувати двійковий код (наприклад, у вас є сторонні бібліотеки без їх джерела), ви також можете використовувати, valgrind
хоча це взагалі повільніше.
Останні компілятори також мають широкі можливості загартовування . Основна відмінність інструментальних бінарних файлів полягає в тому, що перевірки на загартовування розроблені так, щоб вони мали низький вплив на продуктивність (<1%), що робить їх придатними для виробничого коду в цілому. Найвідоміші - це перевірки CFI (Control Flow Integrity), які призначені для фольгування атак, що руйнують стеки, та віртуального виклику вказівників серед інших способів підриву потоку управління.
Суть обох (3) і (4) полягає в перетворенні переривчастої відмови в певний збій : вони обидва слідують принципу швидкого виходу з ладу . Це означає що:
- це завжди виходить з ладу, коли наступаєш на міну
- вона виходить з ладу негайно , вказуючи на помилку, а не на випадкове пошкодження пам'яті тощо.
Поєднання (3) з хорошим тестовим покриттям повинно наздогнати більшість проблем, перш ніж потрапити на виробництво. Використання (4) у виробництві може бути різницею між дратівливою помилкою та подвигом.