Тестирование на переполнение и знак. DEBUG директивы

Существуют ли какие-то директивы компилятора, позволяющие при выполнении арифметики и присвоений целочисленных значений включать проверки на переполнение и присвоение беззнаковому числу отрицательного значения? По типу директив _ITERATOR_DEBUG_LEVEL, CONTAINER_DEBUG_LEVEL (Visual Studio), _GLIBCXX_ASSERTIONS (Qt) для включения проверок на валидность и диапазон в контейнерах и их итераторах. Предполагаю так же, что проверку "отрицательное в беззнаковое" можно интерпретировать, как переполнение, но не знаю, может быть есть уже какое-то устоявшееся мнение по этому поводу.

По сути, я тоже пишу свой контейнер (многомерный массив, в образовательных целях), но кроме указанных проверок хотелось бы еще добавить дэбаги на проверку, что мне не подсовывают отрицательный размер одной из размерностей в конструкторе, но тащить эти проверки в релиз не хочется. И плодить свои директивы не очень красиво, если что-то подобное уже есть в компиляторах.

Всё, что сам нашел на эту тему, это примеры реализации проверок в рантайм. Да, и они мне нужны. Но вопрос не в них. Я хочу, чтобы они (эти проверки) были по умолчанию отключены, но для тестирования можно было бы включить.

Добавлено:

Совсем забыл упомянуть про assert. assert можно было бы, но у него слишком широкий диапазон. Один NDEBUG на всё про всё. Просто, если тут ему место, то почему тогда ему не место в стандартных контейнерах? Не знаю... Но если это действительно место для assert по канону, то пусть будет assert. Но я действительно пока не знаю.


Ответы (3 шт):

Автор решения: HolyBlackCat

Проверки на переполнение входят в -fsanitize=undefined (GCC, Clang; в MSVC вроде нет эквивалента). (Я бы еще добавил -fsanitize=address, который отлавливает неправильную работу с памятью - вот его MSVC умеет.)

По умолчанию там только знаковое (потому что беззнаковое легально), но если покопаться в настройках, возможно его тоже можно отлавливать.


Но, именно для этой цели - проверить, что аргумент неотрицательный - assert() мне нравится больше.

у него слишком широкий диапазон. Один NDEBUG на всё про всё.

Ну тогда сделайте свой макрос в духе assert(), но который можно включать/выключать отдельно.


_GLIBCXX_ASSERTIONS (Qt)

Он не из Qt, он из libstdc++ - стандартной библиотеки С++ компилятора GCC.

Есть еще _GLIBCXX_DEBUG, который включает больше проверок.

→ Ссылка
Автор решения: isnullxbh

Прокомментирую некоторые пункты:

присвоение беззнаковому числу отрицательного значения

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

хотелось бы еще добавить дэбаги на проверку, что мне не подсовывают отрицательный размер одной из размерностей в конструкторе

А как Вам его могут "подсунуть"? Используйте беззнаковый целый тип для данного параметра.

→ Ссылка
Автор решения: user7860670

В других ответах советуют -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
→ Ссылка