Стандарт C не встановлює жодних вимог щодо поведінки в цьому випадку, але багато реалізацій вказують поведінку арифметики покажчика у багатьох випадках, що перевищує мінімальні мінімуми, які вимагає Стандарт, включаючи цей.
Для будь-якої відповідної реалізації С та майже всіх (якщо не всіх) реалізацій С-подібних діалектів, для будь-якого вказівника p
, який *p
або *(p-1)
ідентифікує якийсь об'єкт, діятимуть такі гарантії :
- Для будь-якого цілочисельного значення,
z
яке дорівнює нулю, значення покажчика (p+z)
і (p-z)
буде еквівалентним у будь-якому випадку p
, за винятком того, що вони будуть постійними лише в тому випадку, якщо обидва p
і z
є постійними.
- Для будь-
q
якого, що еквівалентно p
, вирази p-q
і q-p
дадуть нуль.
Наявність таких гарантій для всіх значень покажчика, включаючи null, може усунути необхідність деяких перевірок нуля в коді користувача. Крім того, на більшості платформ генерація коду, який підтримує такі гарантії для всіх значень покажчика без урахування того, чи є вони нульовими, була б простішою та дешевшою, ніж спеціальне лікування нульових значень. Однак деякі платформи можуть захоплювати спроби виконати арифметику покажчиків за допомогою нульових покажчиків, навіть коли додають або віднімають нуль. На таких платформах кількість згенерованих компілятором нульових перевірок, які потрібно було б додати до операцій вказівника для підтримання гарантії, у багатьох випадках значно перевищує кількість створених користувачем нульових перевірок, які в результаті можуть бути опущені.
Якби існувало таке впровадження, де витрати на підтримку гарантій були б великими, але небагато, якщо будь-яка програма отримала б від них якусь вигоду, мало б сенс дозволити їй захоплювати обчислення "нуль + нуль" і вимагати, щоб код користувача для така реалізація включає ручну перевірку нуля, яку гарантії могли б зробити непотрібною. Очікувалось, що така надбавка не вплине на інші 99,44% реалізацій, де вартість підтримання гарантій перевищує вартість. Такі реалізації повинні підтримувати такі гарантії, але їх авторам не потрібно, щоб автори Стандарту повідомляли їм це.
Автори C ++ вирішили, що відповідні реалізації повинні підтримувати вищезазначені гарантії будь-якою ціною, навіть на платформах, де вони можуть істотно погіршити ефективність арифметики покажчика. Вони вирішили, що вартість гарантій навіть на платформах, на яких їх буде дорого підтримувати, перевищує вартість. На таке ставлення могло вплинути бажання розглядати С ++ як мову вищого рівня, ніж С. Програміст змінного струму міг би знати, коли певна цільова платформа незвично обробляє такі випадки, як (нуль + нуль), але програмісти С ++ не очікували, що вони турбуватимуться про такі речі. Гарантування послідовної поведінкової моделі, таким чином, було визнано вартим витрат.
Звичайно, сьогодні питання про те, що «визначено», рідко мають щось спільне з тим, яку поведінку може підтримувати платформа. Натомість зараз модно для компіляторів - в ім'я "оптимізації" - вимагати, щоб програмісти вручну писали код для обробки кутових випадків, з якими платформи раніше обробляли правильно. Наприклад, якщо код, який повинен виводити n
символи, починаючи з адреси p
, записується як:
void out_characters(unsigned char *p, int n)
{
unsigned char *end = p+n;
while(p < end)
out_byte(*p++);
}
старіші компілятори генерували б код, який надійно нічого не видавав би, без побічних ефектів, якщо p == NULL та n == 0, без потреби в спеціальному випадку n == 0. Однак на новіших компіляторах потрібно було б додати додатковий код:
void out_characters(unsigned char *p, int n)
{
if (n)
{
unsigned char *end = p+n;
while(p < end)
out_byte(*p++);
}
}
від якого оптимізатор може позбутися, а може і не вдасться. Якщо не включити зайвий код, деякі компілятори можуть зрозуміти, що оскільки p "не може бути нульовим", будь-які подальші перевірки нульових покажчиків можуть бути опущені, що призведе до того, що код зламається в місці, не пов'язаному з фактичною "проблемою".