Переполнение указателя
Вот пример кода:
int a[2] = {5, 10};
int* b = a + 1;
cout << a << endl;
cout << b + 18446744073709551615UL << endl;
Вывод:
0x7ffd5a82dd30
0x7ffd5a82dd30
Отмечу, что константа в коде - это 2^64 - 1. Как можно объяснить данное поведение? Происходят ли какие-то касты при вычислении этого выражения? Или происходит просто тупое переполнение указателя (который в 64битной системе в памяти имеет по сути такое же представление, как и UL)?
Ответы (1 шт):
Когда вы добавляете к указателю константу, к его фактическому значению прибавляется эта константа, помноженная на размер типа данных, на которые ссылается указатель. В данном случае константа 0xFFFFFFFFFFFFFFFF (шестнадцатиричное представление 18446744073709551615) умножается на 4 (размер типа int), т.е. сдвигается влево на два бита. Старшие два бита при этом теряются, и получается, что к адресу, который является значением указателя, прибавляется число 0xFFFFFFFFFFFFFFFC (18446744073709551612). Т.е., происходит бесзнаковое переполнение! Однако, если представить число 0xFFFFFFFFFFFFFFFC в дополнительном коде (т.е., вычтя его из 2^64), то получим его эквивалент в знаковой трактовке - -4. Т.е., добавляя к адресу число 0xFFFFFFFFFFFFFFFC, вы на деле вычитаете из него число 4. А с точки зрения арифметики указателей языка Си, из указателя b у вас вычитается 1, т.е. вы получаете значение a.
Именно выше описанное и происходит с вашим кодом в архитектуре x86/amd64. Именно по-этому вы получили такой результат. Но тут, как говорится, есть нюанс. В спецификации языка C/C++ вроде как ничего не написано про то, что всё так и должно быть (если я не прав, то пусть меня камрады поправят). А значит и гарантировать полученный результат для всех компиляторов нельзя (не говоря уже про другую архитектуру, в которой переполнение может обрабатываться процессором совершенно иначе). Короче, суть в том, что приведённый вами код в общем случае вызывает неопределённое поведение, UB. Это надо иметь ввиду.