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

С++ сбой в цикле "for" с отрицательным выражением

Следующий код выдает С++ с ошибкой во время выполнения:

#include <string>

using namespace std;

int main() {
    string s = "aa";
    for (int i = 0; i < s.length() - 3; i++) {

    }
}

Пока этот код не сбой:

#include <string>

using namespace std;

int main() {
    string s = "aa";
    int len = s.length() - 3;
    for (int i = 0; i < len; i++) {

    }
}

Я просто не знаю, как это объяснить. Что может быть причиной такого поведения?

4b9b3361

Ответ 1

s.length() - целочисленный тип без знака. Когда вы вычитаете 3, вы делаете его отрицательным. Для unsigned это означает очень большой.

Обходной путь (действительный до тех пор, пока строка длинна до INT_MAX) будет делать следующее:

#include <string>

using namespace std;

int main() {

    string s = "aa";

    for (int i = 0; i < static_cast<int> (s.length() ) - 3; i++) {

    }
}

который никогда не войдет в цикл.

Очень важная деталь заключается в том, что вы, вероятно, получили предупреждение "сравнение значения с подписью и без знака". Проблема в том, что если вы игнорируете эти предупреждения, вы вводите очень опасное поле неявного "целочисленного преобразования" (*) которое имеет но это трудно выполнить: лучше всего никогда не игнорировать эти предупреждения компилятора.


(*) Вам также может быть интересно узнать о "целочисленном продвижении" .

Ответ 2

Прежде всего: почему он падает? Позвольте пройти через вашу программу, как отладчик.

Примечание. Я предполагаю, что тело цикла не пустое, но обращается к строке. Если это не так, причиной сбоя является поведение undefined через переполнение целого числа. Смотрите на это Ричард Хансенс.

std::string s = "aa";//assign the two-character string "aa" to variable s of type std::string
for ( int i = 0; // create a variable i of type int with initial value 0 
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 1!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 2!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 3!
.
.

Мы ожидаем, что проверка i < s.length() - 3 завершится с ошибкой, так как длина s равна двум (мы только каждый присваиваем ей длину в начале и никогда не меняем ее), а 2 - 3 - -1, 0 < -1 является ложным. Однако здесь мы получаем "ОК".

Это потому, что s.length() не 2. Это 2u. std::string::length() имеет тип возврата size_t, который является целым числом без знака. Поэтому, возвращаясь к условию цикла, мы сначала получаем значение s.length(), поэтому 2u, теперь вычитаем 3. 3 является целым литералом и интерпретируется компилятором как тип int. Поэтому компилятор должен рассчитать 2u - 3, два значения разных типов. Операции над примитивными типами работают только для одних и тех же типов, поэтому нужно преобразовать их в другой. Существуют строгие правила, в этом случае unsigned "выигрывает", поэтому 3 преобразуется в 3u. В целых числах без знака 2u - 3u не может быть -1u, поскольку такое число не существует (ну, потому что оно имеет знак, конечно!). Вместо этого он вычисляет каждую операцию modulo 2^(n_bits), где n_bits - количество бит этого типа (обычно 8, 16, 32 или 64). Поэтому вместо -1 получаем 4294967295u (предположим 32 бит).

Итак, теперь компилятор работает с s.length() - 3 (конечно, он намного быстрее меня;-)), теперь отпустите для сравнения: i < s.length() - 3. Ввод значений: 0 < 4294967295u. Опять же, разные типы 0 становятся 0u, сравнение 0u < 4294967295u, очевидно, истинно, условие цикла проверено положительно, теперь мы можем выполнить тело цикла.

После инкремента, единственное, что изменяется в приведенном выше, - это значение i. Значение i снова будет преобразовано в unsigned int, как это требует сравнение.

Итак, мы имеем

(0u < 4294967295u) == true, let do the loop body!
(1u < 4294967295u) == true, let do the loop body!
(2u < 4294967295u) == true, let do the loop body!

Здесь проблема: что вы делаете в теле цикла? Предположительно, вы получаете доступ к символу i^th вашей строки, не так ли? Несмотря на то, что это было не ваше намерение, вы не только получили доступ к нулевому, но и к второму! Второе не существует (поскольку ваша строка содержит только два символа, нулевой и первый), вы получаете доступ к памяти, которой не следует, программа делает все, что захочет (поведение undefined). Обратите внимание, что программа не требуется немедленно сбой. Кажется, он работает нормально еще полчаса, поэтому эти ошибки трудно поймать. Но это всегда опасно для доступа к памяти за пределами границ, именно там происходит большинство сбоев.

Таким образом, вы получаете другое значение от s.length() - 3 от того, что вы ожидаете, это приводит к проверке состояния положительного цикла, что приводит к повторному исполнению тела цикла, который сам по себе обращается к памяти, он должен "т.

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


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

Так как unsigned int довольно длинный и поэтому нежелательно писать снова и снова в циклах, просто используйте size_t. Это тип, который каждый контейнер в STL использует для хранения длины или размера. Возможно, вам потребуется включить cstddef для подтверждения независимости платформы.

#include <cstddef>
#include <string>

using namespace std;

int main() {

    string s = "aa";

    for ( size_t i = 0; i + 3 < s.length(); i++) {
    //    ^^^^^^         ^^^^
    }
}

Так как a < b - 3 математически эквивалентен a + 3 < b, мы можем их заменить. Однако a + 3 < b предохраняет b - 3 от огромного значения. Напомним, что s.length() возвращает целое число без знака, а целые числа без знака выполняют операционный модуль 2^(bits), где бит - это количество бит в типе (обычно 8, 16, 32 или 64). Поэтому с s.length() == 2, s.length() - 3 == -1 == 2^(bits) - 1.


В качестве альтернативы, если вы хотите использовать i < s.length() - 3 для личных предпочтений, вам нужно добавить условие:

for ( size_t i = 0; (s.length() > 3) && (i < s.length() - 3); ++i )
//    ^             ^                    ^- your actual condition
//    ^             ^- check if the string is long enough
//    ^- still prefer unsigned types!

Ответ 3

Собственно, в первой версии вы цикл в течение очень долгого времени, так как вы сравниваете i с целым числом без знака, содержащим очень большое число. Размер строки (по сути) такой же, как size_t, который является целым числом без знака. Когда вы вычитаете значение 3 из этого значения, оно перетекает и становится большим значением.

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

И это не фактически условие или значение, которое вызывает сбой, скорее всего, вы индексируете строку за пределами границ, случай поведения undefined.

Ответ 4

Предполагая, что вы оставили важный код в цикле for

Большинство людей здесь, похоже, неспособны воспроизвести crash — я сам включил — и похоже, что другие ответы здесь основаны на предположении, что вы оставили некий важный код в теле цикла for и что отсутствует код является причиной вашего сбоя.

Если вы используете i для доступа к памяти (предположительно символов в строке) в теле цикла for, и вы оставили этот код из своего вопроса, пытаясь предоставить минимальный пример, авария легко объясняется тем, что s.length() - 3 имеет значение SIZE_MAX из-за модульной арифметики на неподписанных целых типах. SIZE_MAX - очень большое число, поэтому i будет продолжать увеличиваться, пока не будет использовано для доступа к адресу, вызывающему segfault.

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

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

Почему ваша первая программа сработала

Ваша первая программа выйдет из строя, потому что это ее реакция на поведение undefined в вашем коде. (Когда я пытаюсь запустить ваш код, он завершается без сбоев, потому что это моя реакция на поведение undefined.)

Поведение undefined происходит от переполнения int. В стандарте С++ 11 говорится (в параграфе 4 раздела 5):

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

В вашей примерной программе s.length() возвращает значение size_t со значением 2. Вычитая 3 из этого, получим отрицательный 1, кроме size_t - целочисленный тип без знака. В стандарте С++ 11 говорится (в разделе [basic.fundamental] пункт 3.9.1, пункт 4):

Беззнаковые целые числа, объявленные unsigned, должны подчиняться законам арифметики по модулю 2 n где n - количество бит в представлении значений этого конкретного размера целых чисел. 46

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

Это означает, что результатом s.length() - 3 является size_t со значением SIZE_MAX. Это очень большое число, большее, чем INT_MAX (наибольшее значение представлено int).

Поскольку s.length() - 3 настолько велик, выполнение вращается в цикле, пока i не достигнет INT_MAX. На следующей итерации, когда он пытается увеличить i, результат будет INT_MAX + 1, но это не входит в диапазон представляемых значений для int. Таким образом, поведение undefined. В вашем случае происходит сбой в работе.

В моей системе поведение моей реализации, когда i увеличивается до INT_MAX, является обертыванием (установите i в INT_MIN) и продолжайте движение. Как только i достигает -1, обычные арифметические преобразования (С++ [expr], пункт 5, пункт 9) вызывают i равным SIZE_MAX, поэтому цикл завершается.

Любая реакция является подходящей. В этом проблема с undefined behavior &mdash: она может работать так, как вы планируете, она может упасть, может отформатировать ваш жесткий диск или отменить Firefly. Вы никогда не знаете.

Как ваша вторая программа позволяет избежать сбоя

Как и в первой программе, s.length() - 3 - это тип size_t со значением SIZE_MAX. Однако на этот раз значение присваивается int. В стандарте С++ 11 говорится (в [conv.integral] пункт 4.7, пункт 3):

Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.

Значение SIZE_MAX слишком велико, чтобы быть представимым с помощью int, поэтому len получает значение, определенное реализацией (возможно, -1, но, возможно, нет). Условие i < len в конечном итоге будет истинным независимо от значения, присвоенного len, поэтому ваша программа завершится без каких-либо действий undefined.

Ответ 5

Тип s.length() равен size_t со значением 2, поэтому s.length() - 3 также является неподписанным типом size_t и имеет значение SIZE_MAX, которое определено реализацией (который составляет 18446744073709551615, если его размер составляет 64 бит). Он не менее 32-битного типа (может быть 64-битным в 64-разрядных платформах), и это высокое число означает неопределенный цикл. Чтобы предотвратить эту проблему, вы можете просто перевести s.length() в int:

for (int i = 0; i < (int)s.length() - 3; i++)
{
          //..some code causing crash
}

Во втором случае len равен -1, потому что это signed integer, и он не входит в цикл.

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

Ответ 6

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

Чтобы заставить его работать, вы должны указать s.length() как:

static_cast <int> (s.length())

Ответ 7

Возникающая проблема возникает из следующего утверждения:

i < s.length() - 3

Результат s.length() имеет тип без знака size_t. Если вы представляете двоичное представление двух:

0... 010

И затем вы заменяете три из этого, вы эффективно снимаете 1 три раза, то есть:

0... 001

0... 000

Но тогда у вас есть проблема, удаляя третью цифру, которую она переполняет, поскольку она пытается получить другую цифру слева:

1... 111

Это то, что происходит независимо от того, есть ли неподписанный или подписанный тип, однако разница в типе использует наиболее значимые Бит (или MSB) для представления, является ли число отрицательным или нет. Когда происходит undeflow, он просто представляет отрицательный знак для подписанного.

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

Чтобы исправить эту проблему, вы можете манипулировать кодом, который у вас есть несколькими способами:

int main() {
    string s = "aa";
    for (size_t i = 3; i < s.length(); i++) {

    }
}

или

int main() {
    string s = "aa";
    for (size_t i = 0; i + 3 < s.length(); i++) {

    }
}

или даже:

int main() {
    string s = "aa";
    for(size_t i = s.length(); i > 3; --i) {

    }
}

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

Я был склонен представить это как пример кода:

int main() {
    string s = "aa";
    for(size_t i = s.length(); --i > 2;) {

    }
}

После некоторых мы поняли, что это плохая идея. Учение читателей - это выяснить, почему!

Ответ 8

Причина такая же, как и int a = 1000000000; long long b = a * 100000000; даст ошибку. Когда компиляторы умножают эти числа, он оценивает его как int, так как a и буква 1000000000 являются ints, а поскольку 10 ^ 18 намного больше, чем верхняя граница int, это даст ошибку. В вашем случае мы имеем s.length() - 3, так как s.length() - это unsigned int, он не может быть отрицательным, и поскольку s.length() - 3 оценивается как unsigned int, а его значение равно -1, он также дает ошибку.