Generic Containers in C: Safe Division Using Maybe
Показываю, как в C сделать обобщённый контейнер maybe
, который безопасно возвращает результат, если он есть, и сообщает об ошибке, если её нет.
#define maybe(T) struct { bool ok; T value; }
#define maybe_just(T,x) { .value = (x), .ok = true }
#define maybe_nothing(T) { .value = (T){}, .ok = false }
static maybe(int) divide(int a, int b) {
return (b != 0) ? maybe_just(int, a / b) : maybe_nothing(int);
}
Вызов:
maybe(int) p = divide(6, d);
if (p.ok) printf("%d\n", p.value);
else puts("division by zero");
Чтобы не забыть проверку, добавляем макрос maybe_value
, который при ошибке возвращает нулевой указатель, ловим его санитайзером:
#define maybe_value(x) \
(*({ auto _p = &(x); _p->ok ? &_p->value : (void*)0; }))
Но деление на ноль — не единственная проблема. При делении INT_MIN / -1
возникает переполнение. Исправляем:
maybe(int) safe_divide(int a, int b) {
if (b == 0 || (b == -1 && a == INT_MIN))
return maybe_nothing(int);
return maybe_just(int, a / b);
}
Компилируем с -fsanitize=signed-integer-overflow,integer-divide-by-zero -fsanitize-trap=undefined -O2
. В ассемблере не остаётся пути к ud2
, то есть оптимизатор доказал: переполнения и деления на ноль нет.
Это не делает весь C «безопасным» (жизненный цикл указателей и арифметика не покрыты), но для ограниченных задач подход работает.