Тестирование на переполнение и знак. DEBUG директивы
Существуют ли какие-то директивы компилятора, позволяющие при выполнении арифметики и присвоений целочисленных значений включать проверки на переполнение и присвоение беззнаковому числу отрицательного значения? По типу директив _ITERATOR_DEBUG_LEVEL, CONTAINER_DEBUG_LEVEL (Visual Studio), _GLIBCXX_ASSERTIONS (Qt) для включения проверок на валидность и диапазон в контейнерах и их итераторах. Предполагаю так же, что проверку "отрицательное в беззнаковое" можно интерпретировать, как переполнение, но не знаю, может быть есть уже какое-то устоявшееся мнение по этому поводу.
По сути, я тоже пишу свой контейнер (многомерный массив, в образовательных целях), но кроме указанных проверок хотелось бы еще добавить дэбаги на проверку, что мне не подсовывают отрицательный размер одной из размерностей в конструкторе, но тащить эти проверки в релиз не хочется. И плодить свои директивы не очень красиво, если что-то подобное уже есть в компиляторах.
Всё, что сам нашел на эту тему, это примеры реализации проверок в рантайм. Да, и они мне нужны. Но вопрос не в них. Я хочу, чтобы они (эти проверки) были по умолчанию отключены, но для тестирования можно было бы включить.
Добавлено:
Совсем забыл упомянуть про assert. assert можно было бы, но у него слишком широкий диапазон. Один NDEBUG на всё про всё. Просто, если тут ему место, то почему тогда ему не место в стандартных контейнерах? Не знаю... Но если это действительно место для assert по канону, то пусть будет assert. Но я действительно пока не знаю.
Ответы (3 шт):
Проверки на переполнение входят в -fsanitize=undefined
(GCC, Clang; в MSVC вроде нет эквивалента). (Я бы еще добавил -fsanitize=address
, который отлавливает неправильную работу с памятью - вот его MSVC умеет.)
По умолчанию там только знаковое (потому что беззнаковое легально), но если покопаться в настройках, возможно его тоже можно отлавливать.
Но, именно для этой цели - проверить, что аргумент неотрицательный - assert()
мне нравится больше.
у него слишком широкий диапазон. Один
NDEBUG
на всё про всё.
Ну тогда сделайте свой макрос в духе assert()
, но который можно включать/выключать отдельно.
_GLIBCXX_ASSERTIONS (Qt)
Он не из Qt, он из libstdc++ - стандартной библиотеки С++ компилятора GCC.
Есть еще _GLIBCXX_DEBUG
, который включает больше проверок.
Прокомментирую некоторые пункты:
присвоение беззнаковому числу отрицательного значения
Clang/GCC - компилируйте с флагом -Wsign-conversion
:
unsigned foo(int a){ return a; }
// Без -Wsign-conversion: OK
// C -Wsign-conversion: conversion to 'unsigned int' from 'int' may change the sign of the result
хотелось бы еще добавить дэбаги на проверку, что мне не подсовывают отрицательный размер одной из размерностей в конструкторе
А как Вам его могут "подсунуть"? Используйте беззнаковый целый тип для данного параметра.
В других ответах советуют -Wsign-conversion
, однако оно ругается только на неявные преобразования и совершенно безразлично к любым переполнениям. Советуют -fsanitize=undefined
, однако переполнения беззнаковых типов и изменение значения при переходе от знакового типа к беззнаковому являются определенным поведением.
Что же делать? Можно самому писать соотв. проверки и ассерты, однако все уже --украдено-- написано до нас. Например, есть бибилотека SafeInt, предоставляющая одноименный класс для проведения операций с целыми числами с контролем переполнения и прочих проблем, и набор отдельных функций для преобразований и арифметических операций:
SafeInt<unsigned int> size{-3}; // Error
Есть библиотека Boost.Safe Numerics, служащая схожим целям и предоставляющая более продвинутые опции, как то указание диапазона валидных значений для чисел вместо встроенных. Или запрет на компиляцию участков с потерями:
#include <boost/safe_numerics/safe_integer.hpp>
#include <cstdint> // uint8_t
using namespace boost::safe_numerics;
uint8_t f(uint8_t i){
return i;
}
using safe_t = safe<long, native, loose_trap_policy>;
int main(){
const long x = 97;
f(x); // OK - implicit conversion to int can never fail
const safe_t y = 97;
f(y); // could overflow so trap at compile time
return 0;
}
Даже в стандарт С++ проникают некоторые полезные новвовведения, например Saturation arithmetic, позволяющая производить приводящие к переполнению операции с отсечкой по крайнему значению вместо переполнения.
unsigned char x4{::std::add_sat<unsigned char>(253, 4)}; // получается 255