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

Почему арифметическое поведение указателя out-of-bounds undefined?

Следующий пример из Википедии.

int arr[4] = {0, 1, 2, 3};
int* p = arr + 5;  // undefined behavior

Если я никогда не разыскиваю p, то почему поведение arr + 5 только undefined? Я ожидаю, что указатели будут вести себя как целые числа - за исключением того, что при разыменовании значение указателя считается адресом памяти.

4b9b3361

Ответ 1

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

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

Тем не менее, обратите внимание, что выражение, идущее один за концом массива, технически 100% "правильно" и гарантировано не сбой в §5.7 ¶5 спецификации С++ 11. Однако результат этого выражения не указан (просто гарантированно не будет переполнения); в то время как любое другое выражение, идущее больше, чем одно за границами массива, явно выражено undefined.

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

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

Ответ 2

Исходный x86 может иметь проблемы с такими утверждениями. В 16-битном коде указатели имеют 16 + 16 бит. Если вы добавите смещение к младшим 16 бит, вам может потребоваться переполнение и изменение верхних 16 бит. Это была медленная операция, и ее лучше избегать.

В этих системах array_base+offset гарантированно не переполняется, если смещение находится в диапазоне (< = размер массива). Но array+5 будет переполняться, если массив содержит только 3 элемента.

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

Ответ 3

"Undefined поведение" не означает, что он должен упасть на эту строку кода, но это означает, что вы не можете гарантировать, что результат будет гарантирован. Например:

int arr[4] = {0, 1, 2, 3};
int* p = arr + 5; // I guess this is allowed to crash, but that would be a rather 
                  // unusual implementation choice on most machines.

*p; //may cause a crash, or it may read data out of some other data structure
assert(arr < p); // this statement may not be true
                 // (arr may be so close to the end of the address space that 
                 //  adding 5 overflowed the address space and wrapped around)
assert(p - arr == 5); //this statement may not be true
                      //the compiler may have assigned p some other value

Я уверен, что здесь есть много других примеров.

Ответ 4

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

Ответ 5

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

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