Как обнаружить целочисленное переполнение в C - программирование

Как обнаружить целочисленное переполнение в C

Мы знаем, что CPython продвигает целые числа в длинные целые числа (которые допускают арифметику произвольной точности), когда число становится больше.

Как мы можем обнаружить переполнение int и long long в чистом C?

4b9b3361

Ответ 1

Вы не можете обнаружить переполнение со int. Вы должны написать свой код, чтобы избежать этого.

Переполнение со знаком int является неопределенным поведением, и если оно присутствует в вашей программе, программа недействительна, и компилятору не требуется генерировать какое-либо конкретное поведение.

Ответ 2

Вы можете предсказать signed int overflow но пытаться обнаружить его после суммирования слишком поздно. Вы должны проверить на возможное переполнение, прежде чем делать подписанное дополнение.

Невозможно избежать неопределенного поведения, проверяя его после суммирования. Если сложение переполняется, то уже существует неопределенное поведение.

Если бы это был я, я бы сделал что-то вроде этого:

#include <limits.h>

int safe_add(int a, int b) 
{
    if (a >= 0) {
        if (b > (INT_MAX - a)) {
            /* handle overflow */
        }
    } else {
        if (b < (INT_MIN - a)) {
            /* handle underflow */
        }
    }
    return a + b;
}

Обратитесь к этой статье для получения дополнительной информации. Вы также можете выяснить, почему переполнение целых чисел без знака не является неопределенным поведением и что может быть проблемой переносимости в той же статье.

РЕДАКТИРОВАТЬ:

GCC и другие компиляторы имеют некоторые средства для обнаружения переполнения. Например, GCC имеет следующие встроенные функции, позволяющие выполнять простые арифметические операции вместе с проверкой, не переполнены ли операции.

bool __builtin_add_overflow (type1 a, type2 b, type3 *res)
bool __builtin_sadd_overflow (int a, int b, int *res)
bool __builtin_saddl_overflow (long int a, long int b, long int *res)
bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res)
bool __builtin_uadd_overflow (unsigned int a, unsigned int b, unsigned int *res)
bool __builtin_uaddl_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
bool __builtin_uaddll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)

Посетите эту ссылку.

РЕДАКТИРОВАТЬ:

По вопросу, заданному кем-то

Я думаю, было бы неплохо и информативно объяснить, почему переполнение подписанного int не определено, а неподписанное - нет.

Ответ зависит от реализации компилятора. Большинство реализаций C (компиляторов) просто использовали любое поведение переполнения, которое было проще всего реализовать с помощью целочисленного представления, которое оно использовало.

На практике представления для значений со sign-magnitude могут различаться (в зависимости от реализации): one complement, two complement, sign-magnitude. Для беззнакового типа в стандарте нет оснований разрешать изменение, поскольку существует только одно явное binary representation (стандарт допускает только двоичное представление).

Ответ 3

Подписанные операнды должны быть проверены перед выполнением сложения. Вот безопасная функция сложения с двумя сравнениями во всех случаях:

#include <limits.h>

int safe_add(int a, int b) {
    if (a >= 0) {
        if (b > INT_MAX - a) {
            /* handle overflow */
        } else {
            return a + b;
        }
    } else {
        if (b < INT_MIN - a) {
            /* handle negative overflow */
        } else {
            return a + b;
        }
    }
}

Если известно, что тип long long имеет больший диапазон, чем тип int, вы можете использовать этот подход, который может оказаться быстрее:

#include <limits.h>

int safe_add(int a, int b) {
    long long res = (long long)a + b;
    if (res > INT_MAX || res < INT_MIN) {
        /* handle overflow */
    } else {
        return (int)res;
    }
}