C: избегать переполнения при работе с большими числами
Я реализовал некоторые алгоритмы сортировки (для сортировки целых чисел) на C, тщательно используя uint64_t
, чтобы хранить все, что связано с размером данных (таким образом, также счетчики и прочее), поскольку алгоритмы также должны быть протестированы с наборами данных из нескольких гигов целых чисел.
Алгоритмы должны быть точными, и не должно быть никаких проблем с объемом выделенных данных: данные хранятся в файлах, и мы загружаем только небольшие фрагменты за раз, все работает отлично, даже если мы забиваем буферы в памяти любой размер.
Тесты с наборами данных до 4 гигабайт (таким образом, 16 ГБ данных) работают нормально (сортировка 4Gint заняла 2228 секунд, ~ 37 минут), но когда мы выходим выше этого (например, 8 Gints), алгоритм не кажется (он работает около 16 часов).
Я боюсь, что проблема может быть вызвана переполнением целочисленного числа, возможно, счетчик в цикле хранится в 32-битной переменной, или, может быть, мы вызываем некоторые функции, которые работают с 32-битными целыми числами.
Что еще может быть?
Есть ли простой способ проверить, происходит ли переполнение целых чисел во время выполнения?
Ответы
Ответ 1
Это зависит от компилятора, но если вы используете gcc, вы можете скомпилировать с -ftrapv
для выпуска SIGABRT
, когда происходит интегральное переполнение.
Например:
/* compile with gcc -ftrapv <filename> */
#include <signal.h>
#include <stdio.h>
#include <limits.h>
void signalHandler(int sig) {
printf("Overflow detected\n");
}
int main() {
signal(SIGABRT, &signalHandler);
int largeInt = INT_MAX;
int normalInt = 42;
int overflowInt = largeInt + normalInt; /* should cause overflow */
/* if compiling with -ftrapv, we shouldn't get here */
return 0;
}
Когда я запускаю этот код локально, вывод
Overflow detected
Aborted
Ответ 2
Посмотрите -ftrapv
и -fwrapv
:
-ftrapv
Эта опция генерирует ловушки для подписанного переполнения для операций сложения, вычитания, умножения.
-fwrapv
Этот параметр указывает компилятору предположить, что подписанное арифметическое переполнение сложения, вычитания и умножения обертывается с использованием представления двухкомпонента. Этот флаг допускает некоторые оптимизации и отключает другие. Эта опция включена по умолчанию для интерфейса Java, как того требует спецификация языка Java.
См. также Переполнение целых чисел в стандартах и компиляторах C: и Полезные флаги GCC для C.
Ответ 3
Если вы используете компилятор Microsoft, есть опции для генерации кода, который запускает исключение SEH, когда целочисленное преобразование отсекает ненулевые биты. В тех местах, где это действительно необходимо, используйте побитовое И, чтобы удалить верхние биты перед выполнением преобразования.
Ответ 4
clang теперь поддерживает проверку динамического переполнения как для целых чисел, так и без знака. См. - fsanitize = integer. На данный момент это только один компилятор С++ с полностью поддерживаемой проверкой динамического переполнения для целей отладки.
Ответ 5
Единственный верный способ - обернуть операции над этими целыми числами в функции, которые выполняют проверку нарушения границ. Это, конечно, замедлит операции с целым числом, но если ваш код утверждает или останавливается на нарушении границы с содержательным сообщением об ошибке, это будет иметь большое значение для того, чтобы помочь вам определить, где проблема.
Что касается вашей конкретной проблемы, имейте в виду, что сортировка общего случая - это O (nlogn), поэтому причина, по которой алгоритм занимает гораздо больше времени, может быть вызвана тем, что увеличение времени не является линейным по отношению к данным комплект размер. Поскольку также не упоминалось, сколько физической памяти находится в ящике и сколько ее используется для вашего алгоритма, возможно, возникла ошибка страницы на диске с большим набором данных, что потенциально замедлит сканирование.