Це невизначена поведінка для друку нульових покажчиків за допомогою %p
специфікатора перетворення?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
Питання стосується стандарту С, а не реалізації С.
Це невизначена поведінка для друку нульових покажчиків за допомогою %p
специфікатора перетворення?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
Питання стосується стандарту С, а не реалізації С.
Відповіді:
Це один із тих дивних випадків, коли ми поширюємось на обмеження англійської мови та непослідовну структуру стандарту. Тож у кращому випадку я можу навести переконливий контраргумент, оскільки це неможливо довести :) 1
Код у питанні демонструє чітко визначену поведінку.
Оскільки [7.1.4] є основою питання, почнемо з цього:
Кожне з наступних тверджень застосовується, якщо прямо не зазначено інше в детальних описах, що подаються нижче: Якщо аргумент функції має недійсне значення ( наприклад, значення поза доменом функції або покажчик поза адресним простором програми, або нульовий покажчик , [... інші приклади ...] ) [...] поведінка невизначена. [... інші твердження ...]
Це незграбна мова. Одне з тлумачень полягає в тому, що елементи у списку є UB для всіх функцій бібліотеки, якщо це не замінено окремими описами. Але список починається з "типу", вказуючи на те, що він наочний, а не вичерпний. Наприклад, у ньому не згадується правильне закінчення нульових рядків (критично для поведінки, наприклад strcpy
).
Таким чином, очевидно, що намір / область застосування 7.1.4 просто полягає в тому, що "недійсне значення" веде до UB ( якщо не вказано інше ). Ми повинні розглянути опис кожної функції, щоб визначити, що вважається "недійсним значенням".
strcpy
[7.21.2.3] говорить лише це:
В
strcpy
функція копіює рядок , зазначенуs2
(включаючи завершальний нульовий символ) в масив , на який вказуєs1
. Якщо копіювання відбувається між об'єктами, що перекриваються, поведінка не визначена.
У ньому не згадується явних нульових покажчиків, але не згадується також і про нульові термінатори. Натомість один робить висновок із "рядка, на який вказуєs2
", що єдиними допустимими значеннями є рядки (тобто вказівники на масиви символів, що закінчуються нулем).
Дійсно, цю закономірність можна побачити в окремих описах. Деякі інші приклади:
[7.6.4.1 (fenv)] зберегти поточну середу з плаваючою точкою в зазначених вище об'єкта шляхом
envp
[7.12.6.4 (frexp)] зберігати ціле число в Int зазначених вище об'єкта з допомогою
exp
[7.19.5.1 (fclose)] потік вказав на
stream
printf
[7.19.6.1] говорить про %p
:
p
- Аргумент повинен бути вказівником наvoid
. Значення покажчика перетворюється на послідовність друку символів, визначеним реалізацією.
Null є дійсним значенням покажчика, і в цьому розділі явно не згадується, що null є особливим випадком, і що вказівник повинен вказувати на об'єкт. Таким чином визначається поведінка.
1. Якщо не виступить автор стандартів, або якщо ми не знайдемо щось подібне до обґрунтованого документа, який пояснює речі.
Так . Друк нульових покажчиків за допомогою %p
специфікатора перетворення має невизначену поведінку. Сказавши це, я не знаю жодної відповідної реалізації, яка могла б поводитися неправильно.
Відповідь стосується будь-якого зі стандартів С (C89 / C99 / C11).
Специфікатор %p
перетворення очікує, що аргумент вказівника типу буде void, перетворення вказівника на друкувані символи визначається реалізацією. Це не говорить, що очікується нульовий покажчик.
Вступ до стандартних функцій бібліотеки стверджує, що нульові вказівники як аргументи функцій (стандартної бібліотеки) вважаються недійсними значеннями, якщо прямо не зазначено інше.
C99
/ C11
§7.1.4 p1
[...] Якщо аргумент функції має недійсне значення (наприклад, [...] нульовий покажчик, [...] поведінка не визначена.
Приклади функцій (стандартної бібліотеки), які очікують нульові вказівники як дійсні аргументи:
fflush()
використовує нульовий покажчик для змивання "всіх потоків" (що застосовуються).freopen()
використовує нульовий вказівник для позначення файлу, "який на даний момент пов'язаний" із потоком.snprintf()
дозволяє передавати нульовий покажчик, коли 'n' дорівнює нулю.realloc()
використовує нульовий покажчик для виділення нового об'єкта.free()
дозволяє передавати нульовий покажчик.strtok()
використовує нульовий покажчик для наступних викликів.Якщо взяти випадок за snprintf()
, має сенс дозволити передачу нульового вказівника, коли 'n' дорівнює нулю, але це не так для інших функцій (стандартної бібліотеки), які допускають подібний нуль 'n'. Наприклад: memcpy()
, memmove()
, strncpy()
, memset()
, memcmp()
.
Це не тільки зазначено у вступі до стандартної бібліотеки, але також ще раз у вступі до цих функцій:
C99 §7.21.1 p2
/ C11 §7.24.1 p2
Якщо аргумент, оголошений як
size_t
n, визначає довжину масиву для функції, n може мати значення нуля при виклику цієї функції. Якщо прямо не зазначено інше в описі певної функції в цьому підпункті, аргументи покажчика для такого виклику все одно повинні мати дійсні значення, як описано в 7.1.4.
Я не знаю, чи UB %p
з нульовим покажчиком насправді є навмисним, але оскільки стандарт явно вказує, що нульові вказівники вважаються недійсними значеннями як аргументи стандартних функцій бібліотеки, а потім він іде і явно вказує випадки, коли нульовий покажчик є дійсним аргументом (snprintf, безкоштовно, і т.д.), а потім він йде і ще раз повторює вимогу аргументи дійсними навіть в нулі випадків «п» ( memcpy
, memmove
, memset
), то я думаю , що це розумно припустити , що Комітет з питань стандартів С не надто стурбований тим, що такі речі не визначені.
%p
не повинно бути невизначеною поведінкою
Автори стандарту С не докладали зусиль, щоб вичерпно перерахувати всі поведінкові вимоги, яким має відповідати реалізація, щоб бути придатною для якоїсь конкретної мети. Натомість вони очікували, що люди, які пишуть компілятори, будуть проявляти певну кількість здорового глузду незалежно від того, вимагає це Стандарт чи ні.
Питання про те, чи щось викликає UB, рідко є самим собою і корисним. Справжні важливі питання:
Чи повинен хтось, хто намагається написати якісний компілятор, зробити так, щоб він поводився передбачувано? Для описаного сценарію відповідь однозначно так.
Чи повинні програмісти мати право очікувати, що якісні компілятори для будь-чого, що нагадує звичайні платформи, будуть поводитися передбачувано? В описаному сценарії я б сказав, що відповідь так.
Чи не можуть деякі тупі автори компіляторів розтягнути інтерпретацію Стандарту, щоб виправдати щось дивне? Я сподівався б, що ні, але не виключав би цього.
Чи повинні дезінфікуючі компілятори пирхати про поведінку? Це залежатиме від рівня параної у їхніх користувачів; дезінфікуючий компілятор, мабуть, не повинен за замовчуванням пирхати про таку поведінку, але, можливо, надає можливість конфігурації, якщо програми можуть бути перенесені на "розумні" / німі компілятори, які поводяться дивно.
Якщо розумна інтерпретація Стандарту передбачає поведінку, але деякі автори компіляторів розширюють інтерпретацію, щоб виправдати інше, чи справді важливо, що говорить Стандарт?
printf("%p", (void*) 0)
невизначена поведінка чи ні, згідно зі стандартом? Глибоко вкладені виклики функцій мають таке ж значення, як і ціна чаю в Китаї. І так, UB дуже поширений у реальних програмах - що з цього?
%p
кожного можливого значення запитання.