У найсуворішому сенсі це не визначена поведінка, а визначена реалізацією. Тож, хоч і не доцільно, якщо ви плануєте підтримувати непрофільні архітектури, ви, ймовірно, можете це зробити.
Стандартна цитата, подана interjay, є хорошою, що вказує на UB, але це лише другий найкращий хіт, на мій погляд, оскільки він стосується арифметики покажчика-покажчика (смішно, один явно є UB, а інший - ні). У запитанні є абзац, що стосується операції безпосередньо:
[expr.post.incr] / [expr.pre.incr]
Операнд повинен бути [...] або вказівником на повністю визначений тип об'єкта.
О, почекайте мить, повністю визначений тип об’єкта? Це все? Я маю на увазі, справді, тип ? Тож вам об’єкт взагалі не потрібен?
Потрібно зовсім небагато читати, щоб насправді знайти натяк на те, що щось там може бути не таким добре визначеним. Тому що поки що він читає так, як ніби тобі дозволено це робити, жодних обмежень.
[basic.compound] 3
робить заяву про те, який тип покажчика може бути, а як жоден з трьох інших, результат вашої операції явно підпадав би під 3.4: недійсний покажчик .
Однак це не говорить про те, що вам заборонено мати недійсний покажчик. Навпаки, у ньому перераховані деякі дуже поширені нормальні умови (наприклад, кінець тривалості зберігання), коли покажчики регулярно стають недійсними. Тож, мабуть, це може відбутися. І справді:
[basic.stc] 4
Непряме значення вказівника через недійсне значення вказівника та передача недійсного значення вказівника функції делокації мають невизначене поведінку. Будь-яке інше використання недійсного значення вказівника має поведінку, визначену реалізацією.
Ми робимо там "будь-який інший", тож це не визначена поведінка, а визначена реалізацією, таким чином загалом допустима (якщо тільки в реалізації явно не сказано щось інше).
На жаль, це ще не кінець історії. Хоча чистий результат з цього моменту більше не змінюється, він стає більш заплутаним, чим довше ви шукаєте "покажчик":
[basic.compound]
Дійсне значення типу вказівника об'єкта являє собою або адресу байта в пам'яті, або нульовий вказівник. Якщо об’єкт типу T розташований за адресою A [...], як кажуть, вказує на цей об’єкт, незалежно від того, як було отримано значення .
[Примітка. Наприклад, адреса, що минає наприкінці масиву, вважатиметься вказівкою на непов'язаний об'єкт типу елемента масиву, який може бути розташований за цією адресою. [...]].
Читайте як: Добре, хто дбає! Поки вказівник десь вказує на пам’ять , я добре?
[basic.stc.dynamic.safety] Значення вказівника - це безпечно отриманий покажчик [бла-бла]
Читайте як: Гаразд, безпечно отриманий, що завгодно. Це не пояснює, що це таке, а також не каже, що мені насправді це потрібно. Безпечно отриманий-чорт. Мабуть, я все ще можу мати непогано отримані вказівники просто чудово. Я здогадуюсь, що перенаправлення їх, мабуть, не буде такою вдалою ідеєю, але цілком допустимо їх мати. Це не говорить інакше.
Реалізація може мати послаблену безпеку вказівника, і в цьому випадку дійсність значення вказівника не залежить від того, чи це безпечно отримане значення вказівника.
О, так це може не мати значення, тільки те, що я думав. Але зачекайте ... "не може"? Це означає, що це також може бути . Звідки я знаю?
Альтернативно, реалізація може мати сувору безпеку вказівника; у цьому випадку значення вказівника, яке не є безпечно виведеним значенням вказівника, є недійсним значенням вказівника, якщо посилається повний об'єкт не має динамічної тривалості зберігання і раніше не було оголошено доступним
Зачекайте, тож навіть можливо, що мені потрібно дзвонити declare_reachable()
на кожен покажчик? Звідки я знаю?
Тепер ви можете конвертувати в intptr_t
, що є чітко визначеним, даючи ціле уявлення безпечно отриманого вказівника. Для яких, звичайно, будучи цілим числом, цілком законно і чітко визначено збільшувати його як завгодно.
І так, ви можете перетворити intptr_t
спинку в покажчик, який також добре визначений. Тільки просто, не будучи початковим значенням, вже не гарантується, що у вас є безпечно отриманий покажчик (очевидно). Все-таки, що стосується літери стандарту, хоча це визначається впровадженням, це 100% законна справа:
[expr.reinterpret.cast] 5
Значення інтегрального типу або типу перерахування можна явно перетворити в покажчик. Вказівник, перетворений на ціле число достатнього розміру [...] і назад до того самого типу вказівника [...] початкового значення; відображення між покажчиками та цілими числами інакше визначено реалізацією.
Улов
Покажчики - це просто звичайні цілі числа, ви лише використовуєте їх як покажчики. О, якби тільки це було правдою!
На жаль, існують архітектури, де це зовсім не відповідає дійсності, а просто генерування недійсного вказівника (не дереферентування, просто наявність у реєстрі покажчиків) спричинить пастку.
Отже, це основа "реалізації визначено". Це, а також той факт , що збільшується покажчик щоразу , коли ви хочете, як ви , будь ласка , може , звичайно , перелив причини, які стандарт не хоче мати справу з. Кінець адресного простору програми може не збігатися з місцем розташування переповнення, і ви навіть не знаєте, чи є така річ, як переповнення покажчиків певної архітектури. Загалом це кошмарний безлад не в жодному відношенні можливих вигод.
Справа з умовою «один минулий об’єкт» з іншого боку, проста: реалізація повинна просто переконатися, що жоден об’єкт ніколи не виділяється, щоб останній байт в адресному просторі був зайнятий. Тож це чітко визначено, оскільки це корисно і банально гарантувати.