Подтвердить что ты не робот

Подписанные и неподписанные значения для подсчета в цикле

Итак, у меня внутри программы обычная для цикла через вектор объектов (объекты, которые имеют тип I, если это имеет значение):

for(int k = 0; k < objects.size(); k++){ ... }

... и когда я скомпилирую, я получаю это предупреждение:

warning: comparison between signed and unsigned integer expressions 

Это имеет смысл, так как я думаю, что size() для вектора возвращает a size_t. Но почему это имеет значение? Не является ли определенное количество элементов (или даже блоков памяти) целым числом, которое вы можете подсчитать? Что еще более важно, так как моя программа имеет несколько таких циклов и часто случается с segfault, может ли это быть частью этого?

4b9b3361

Ответ 1

Проблема возникает, когда object.size() возвращает значение, превышающее максимальное отображаемое значение k. Поскольку знак k подписан, он имеет только половину максимального значения по сравнению с a size_t 1.

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

1. Упреждающее опровержение: Да, это справедливо только для машин, использующих типичную арифметику с двумя дополнениями, и для машин, где int и size_t представлены с использованием того же количества бит.

Ответ 2

Хорошо ответил, но я добавлю свой S/0.02: "Правильный" способ сделать это:

for (typename std::vector<MyObject>::size_type i = 0; i < object.size(); ++i) { ... }

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

С С++ 11 вы можете воспользоваться decltype:

for (decltype(object.size()) i = 0; i < object.size(); ++i) { ... }

Или вы можете воспользоваться auto:

for (auto i = object.size() - object.size(); i < object.size(); ++i) { ... }

Или вы можете просто использовать size_t, но у вас все еще могут быть сомнения относительно переполнения, так как vector<MyObject> size_type может быть больше размера size_t. (Это не так, но нет никаких гарантий):

for (size_t i = 0; i < object.size(); ++i) { ... }

Итак, что делать честному программисту?

Абсолютно простое решение - это тот, который STL продвигает с самого начала. За исключением того, что в начале было также больно писать:

for (typename std::vector<MyObject>::iterator_type it = object.begin(); it != object.end(); ++it) { ... }

Теперь С++ 11 действительно вам поможет. У вас есть очень хорошие альтернативы, начиная с простого:

for (auto it = object.begin(); it != object.end(); ++it) { ... }

Но он становится еще лучше (барабан, пожалуйста)...:

for (auto& val : object) { ... }

И тот, который я использую.


Отредактировано для добавления:

Кори Нельсон в комментарии указывает, что также можно кэшировать результат object.end() с помощью:

for (auto it = object.begin(), end = object.end(); it != end; ++it) { ... }

Оказывается, код, сгенерированный синтаксисом for (var : object), очень похож на код, предложенный Кори Нельсоном. (Поэтому я бы посоветовал ему и вам просто использовать последнее.)

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

Единственный способ итерации вектора, который может быть изменен во время итерации, - использовать целые индексы, как в исходном сообщении. Другие контейнеры более прощающие. Вы можете перебирать STL-карту с циклом, который вызывает object.end() на каждой итерации, и (насколько я знаю) он будет работать даже перед лицом вставок и удалений, но не пытайтесь с помощью unordered_map, или вектор. Он работает с deque, если вы всегда нажимаете на конец и выпадаете по фронту, что удобно, если вы используете deque в качестве очереди в первую очередь; Я не уверен, если вы можете уйти с появлением deque сзади.

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

Ответ 3

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

Ответ 4

Важные предупреждения могут быть потеряны в потоке предупреждений о сопоставленных/неподписанных сравнениях для переменных цикла. И даже некоторые подписанные/неподписанные предупреждения о сравнении важны! Итак, избавитесь от несущественных предупреждений, указав функцию размера, например:

#include <stddef.h>    // ptrdiff_t
#include <utility>     // std::begin, std::end

typedef ptrdiff_t Size;
typedef Size Index;

template< class Type >
Size nElements( Type const& c )
{
    using std::begin;  using std::end;
    return end( c ) - begin( c );
}

Затем вы можете просто написать, например.

for( int i = 0;  i < nElements( v );  ++i ) { ... }

В качестве альтернативы используйте итераторы, например.

for( auto it = begin( v );  it != end( v );  ++it ) { ... }

И/или используйте цикл for на основе С++ 11,

for( auto const& elem : v ) { ... }

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

Еще одна область, на которую вы должны обратить внимание, - это приведение стиля C: избавиться от них!; -)