2015-10-31 2 views
4

Как мы знаем, такой код будет генерировать предупреждение:Почему компиляторы не делают unsigned vs signed сравнения безопасными?

for (int i = 0; i < v.size(); ++i) 

решения что-то вроде auto i = 0u;, decltype(v.size()) или std::vector<int>::size_type, но делает вид, что мы вынуждены иметь как подписанное и значение без знака. Компилятор автоматически добавит int в unsigned int (фактический тип не имеет значения). Используя явный листинг, static_cast<unsigned int>(i) заставляет предупреждение уйти, но это плохо, потому что он сделал то же самое, что сделал компилятор, и заставил замолчать важное предупреждение!

Чем лучше решение:

if ((i < 0) || (static_cast<unsigned int>(i) < v.size())) 

Вполне понятно, что С «ближе к металлу», и, как следствие, более небезопасным. Но в C++ для этого нет оправдания. Поскольку C++ и C расходятся (как они это делали в течение многих лет), сотни улучшений на C++ повысили безопасность. Я очень сомневаюсь, что такое изменение может повредить производительности.

Есть ли причина, по которой компиляторы не делают это автоматически?

N.B: Это происходит в реальном мире. См. Vulnerability Note VU#159523:

Эта уязвимость в Adobe Flash возникает из-за того, что Flash передает целое число со знаком в calloc(). Злоумышленник контролирует это целое число и может отправлять отрицательные числа. Поскольку calloc() принимает size_t, который без знака, отрицательное число преобразуется в очень большое число, которое обычно слишком велико для выделения, и в результате calloc() возвращает NULL, что приводит к существованию уязвимости.

+0

Часто вы можете написать код, чтобы не допустить этого. Используйте итераторы, диапазоны для циклов, стандартные алгоритмы или, если вам нужно подсчитать, используйте 'size_t'. –

+0

«Я очень сомневаюсь, что такое изменение может повредить работе» - если это приведет к появлению большего количества инструкций, это повредит производительности. Даже если выполнение этих инструкций перекрывается, кеш остается конечным. Условия были * намного хуже, когда эти правила были впервые написаны на камне. –

+1

Re. Уязвимость: похоже, настоящая проблема заключается в том, что Flash не проверяет результат calloc? –

ответ

1

Важной целью C++ является совместимость, и есть тонна кода, которая не будет компилироваться, если смешение под подписью/без знака было фатальной ошибкой. См. Stroustrup’s C++ Design Goals in 1986.

Ваше предложение также добавляет сравнение, которого нет в источнике.

Возможно с C++ 11 этот случай уже сделано более безопасным, если вы использовали варьировались-за и auto:

for (auto i : v) 
+0

Является ли предложенный мной код фатальной ошибкой? Я просто говорю, что заменить текущее поведение на более безопасное поведение, а не предупреждать об ошибке. – user5508973

+0

Ваш код уже небезопасен, если вы строите с 64-битным, где '' size_t'' является 64-битным, а '' unsigned int'' - 32-разрядным ... скажем, Windows x64. –

+1

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

1

В настоящее время C++ правила для неявных преобразований делают беззнаковое целое число типов опасных для использования в качестве числа. Канонический пример, что string("hello").size() < -5" гарантировано, что глупое – но не только глупо. Время, потраченное на то, чтобы отслеживать тонкие ошибки, связанные с такими преобразованиями, не говоря уже о времени, потраченном на то, чтобы выбрать идеально подходящие типы, говорит, что это гораздо более серьезное, чем глупое поведение в составленном примере; это примерно так же серьезно, как и в программировании.

Плохое решение состоит в том, чтобы разместить неряшливых программистов, добавив дополнительные сравнения, где эти программисты напрямую сравнивают подписанный с неподписанным. Это плохо, потому что C++ основан на идее обслуживания квалифицированных программистов, которые знают, что они делают, и хотят как можно меньше между своим кодом и полученным машинным кодом. Сравнение подписанного с unsigned имеет простые четко определенные эффекты в текущем C++, а не эффекты, например, может ожидать Java-программист.

Лучшим решением для программиста является улучшение, прямое выражение того, что он хочет, и не полагаться на волшебные невидимые исправления.

Например, один хороший способ выразить намерение - сделать результат size операций подписанным типом, например. ptrdiff_t, или когда эта полная общность не нужна, просто int.

И один хороший способ поддержки, который должен определить n_items функцию следующим образом:

using Size = ptrdiff_t; 

template< class Container > 
auto n_items(Container& c) 
    -> Size 
{ return c.size(); } 

Цены не поддерживает, например, std::list можно сделать работу для необработанных массивов без специализации, используя end(c) - begin(c). Но я предпочитаю иметь отдельную специализацию (1). Вот так:

template< class Item, Size n > 
auto n_items(Item (&)[n]) 
    -> Size 
{ return n; } 

Отказ от ответственности: с кодом манжеты, не касаясь руками компилятора.


1) Ну, технически это перегрузка, а не специализация. Но я следую терминологическому руководству бывшего секретаря комитета по стандартизации Пете Беккера. Я., давайте использовать простой описательный язык, где можем, и прибегать к формальным терминам, когда это необходимо.

+0

Текущие правила C++ делают целые числа со знаком небезопасными для каких-либо целей из-за смешных неопределенных правил поведения. Я добавлял 'u' к как можно большему количеству мест в моем коде, чтобы избежать этих проблем. – Myria

+0

@Myria: Использование неподписанных повсюду вытесняет ряд серьезных проблем для очень маргинальных задач UB со знакомным переполнением. На практике, что UB является лишь проблемой в отношении оптимизации для некоторых компиляторов (я использую множественное число, но у меня есть знание из первых рук только одного). Хорошим решением является использование соответствующих параметров компилятора, чтобы сделать этот компилятор более практичным, где (если вообще) необходимо. –

Смежные вопросы