(Как) вы обрабатываете возможные целочисленные переполнения в коде C ++? - PullRequest
6 голосов
/ 26 июля 2011

Время от времени, особенно при выполнении 64-битных сборок некоторой базы кода, я замечаю, что во многих случаях возможны целочисленные переполнения.Наиболее распространенным случаем является то, что я делаю что-то вроде этого:

// Creates a QPixmap out of some block of data; this function comes from library A
QPixmap createFromData( const char *data, unsigned int len );

const std::vector<char> buf = createScreenShot();
return createFromData( &buf[0], buf.size() ); // <-- warning here in 64bit builds

Дело в том, что std::vector::size() приятно возвращает size_t (что составляет 8 байтов в 64-битных сборках), но функция получаетunsigned int (что составляет всего 4 байта в 64-битных сборках).Таким образом, компилятор предупреждает правильно.

Если возможно, я пытаюсь исправить сигнатуры, чтобы в первую очередь использовать правильные типы.Однако я часто сталкиваюсь с этой проблемой при объединении функций из разных библиотек, которые я не могу изменить.К сожалению, я часто прибегаю к некоторым рассуждениям в духе «Хорошо, никто никогда не сделает снимок экрана, генерирующий более 4 ГБ данных, так зачем беспокоиться» и просто поменяю код на

return createFromData( &buf[0], static_cast<unsigned int>( buf.size() ) );

, чтобыкомпилятор отключается.Тем не менее, это чувствует себя действительно злым.Так что я подумывал о том, чтобы иметь какое-то утверждение во время выполнения, которое, по крайней мере, приводит к приятной ошибке в сборках отладки, например:

assert( buf.size() < std::numeric_limits<unsigned int>::maximum() );

Это уже немного лучше, но мне интересно: как сделатьВы имеете дело с такой проблемой, а именно: целочисленные переполнения, которые «практически» невозможны (на практике).Я предполагаю, что это означает, что они не происходят для вас, они не происходят для QA - но они взрываются перед лицом клиента.

Ответы [ 5 ]

4 голосов
/ 26 июля 2011

Если вы не можете исправить типы (потому что вы не можете нарушить совместимость библиотеки), и вы «уверены», что размер никогда не станет таким большим, вы можете использовать boost::numeric_cast вместо static_cast. Это вызовет исключение, если значение слишком большое.

Конечно, окружающий код должен делать что-то неопределенно разумное, за исключением исключения - поскольку это условие "не ожидается когда-либо", это может означать просто корректное завершение работы. Все же лучше, чем продолжать с неправильным размером.

2 голосов
/ 26 июля 2011

Решение зависит от контекста. В некоторых случаях вы знаете, где данные происходит и может исключить переполнение: int, который инициализируется с 0 и увеличивается один раз в секунду, например, не будет переполняться в любое время жизни машины. В таких случаях вы просто конвертируете (или позволить неявному преобразованию делать свое дело), ​​и не волнуйтесь об этом.

Другой тип случая довольно похож: в вашем случае, например, это Снимок экрана, вероятно, не имеет смысла иметь больше данных, которые могут быть обозначается int, поэтому преобразование также безопасно. При условии данные действительно пришли с снимка экрана; в таких случаях обычный Процедура заключается в проверке данных на входе, гарантируя, что он выполняет ваши ограничения вниз по течению, а затем не проводить дальнейшую проверку.

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

1 голос
/ 26 июля 2011

Если вы помещаете 64-битное переполненное число в 32-битную библиотеку, вы открываете ящик Пандоры - неопределенное поведение.

Выдает исключение.Поскольку исключения могут возникать произвольно в любом месте, у вас должен быть подходящий код для его перехвата в любом случае .Учитывая это, вы также можете использовать его.

Сообщения об ошибках неприятны, но лучше, чем неопределенное поведение.

0 голосов
/ 26 июля 2011

Одна вещь пришла мне в голову: поскольку мне нужна какая-то проверка во время выполнения (может ли значение, например, buf.size() превышать диапазон unsigned int, быть проверено только во время выполнения), но я не хочучтобы иметь миллион assert() вызовов везде, я мог бы сделать что-то вроде

template <typename T, typename U>
T integer_cast( U v ) {
    assert( v < std::numeric_limits<T>::maximum() );
    return static_cast<T>( v );
}

Таким образом, я бы по крайней мере централизовал утверждение, а

return createFromData( &buf[0], integer_cast<unsigned int>( buf.size() ) );

Это немноголучше.Возможно, я должен вместо этого бросить исключение (оно действительно исключительное!) Вместо assert ', чтобы у вызывающего абонента была возможность изящно справиться с ситуацией путем отката предыдущей работы и выдачи диагностического вывода или тому подобного.

0 голосов
/ 26 июля 2011

Такие сценарии могут быть выполнены одним из четырех способов или с использованием их комбинации:

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...