Czy drukowanie wskaźników o wartości null ze specyfikatorem %p
konwersji jest niezdefiniowane ?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
Pytanie dotyczy standardu C, a nie implementacji C.
c
language-lawyer
c99
undefined-behavior
c11
Dror K.
źródło
źródło
Odpowiedzi:
Jest to jeden z tych dziwnych przypadków narożnych, w których podlegamy ograniczeniom języka angielskiego i niespójnej strukturze w standardzie. W najlepszym razie mogę przedstawić przekonujący kontrargument, którego nie da się udowodnić :) 1
Kod w pytaniu wykazuje dobrze zdefiniowane zachowanie.
Ponieważ podstawą pytania jest [7.1.4] , zacznijmy od tego:
To jest niezdarny język. Jedną z interpretacji jest to, że pozycje na liście są UB dla wszystkich funkcji bibliotecznych, chyba że zostaną zastąpione przez indywidualne opisy. Ale lista zaczyna się od „takich jak”, co oznacza, że ma charakter poglądowy, a nie wyczerpujący. Na przykład nie wspomina o poprawnym zakończeniu zerowania ciągów (krytycznych dla zachowania np
strcpy
.).Dlatego jasne jest, że celem / zakresem 7.1.4 jest po prostu to, że „nieprawidłowa wartość” prowadzi do UB ( chyba że zaznaczono inaczej ). Musimy spojrzeć na opis każdej funkcji, aby określić, co liczy się jako „nieprawidłowa wartość”.
Przykład 1 -
strcpy
[7.21.2.3] mówi tylko tak:
Nie wspomina wyraźnie o zerowych wskaźnikach, ale nie wspomina też o zerowych terminatorach. Zamiast tego można wywnioskować z „łańcucha wskazywanego przez
s2
”, że jedynymi poprawnymi wartościami są łańcuchy (tj. Wskaźniki do tablic znaków zakończonych znakiem null).Rzeczywiście, ten wzór można zobaczyć w poszczególnych opisach. Kilka innych przykładów:
Przykład 2 -
printf
[7.19.6.1] mówi tak o
%p
:Null jest prawidłową wartością wskaźnika, aw tej sekcji nie ma wyraźnej wzmianki, że null jest przypadkiem specjalnym, ani że wskaźnik musi wskazywać na obiekt. Tak więc jest to określone zachowanie.
1. Chyba że zgłosi się autor standardów lub jeśli nie znajdziemy czegoś podobnego do dokumentu uzasadniającego wyjaśnienia.
źródło
Krótka odpowiedź
Tak . Drukowanie wskaźników zerowych ze specyfikatorem
%p
konwersji ma niezdefiniowane zachowanie. Powiedziawszy to, nie znam żadnej istniejącej implementacji, która mogłaby się źle zachować.Odpowiedź dotyczy dowolnego z norm C (C89 / C99 / C11).
Długa odpowiedź
Specyfikator
%p
konwersji oczekuje argumentu typu wskaźnik na void, konwersja wskaźnika do drukowalnych znaków jest zdefiniowana przez implementację. Nie stwierdza, że oczekiwany jest pusty wskaźnik.We wprowadzeniu do funkcji biblioteki standardowej stwierdza się, że wskaźniki puste jako argumenty funkcji (biblioteki standardowej) są uważane za nieprawidłowe wartości, chyba że wyraźnie określono inaczej.
C99
/C11
§7.1.4 p1
Przykłady funkcji (biblioteki standardowej), które oczekują zerowych wskaźników jako prawidłowych argumentów:
fflush()
używa pustego wskaźnika do opróżniania „wszystkich strumieni” (które mają zastosowanie).freopen()
używa pustego wskaźnika do wskazania pliku „aktualnie skojarzonego” ze strumieniem.snprintf()
umożliwia przekazanie pustego wskaźnika, gdy „n” jest równe zero.realloc()
używa pustego wskaźnika do przydzielania nowego obiektu.free()
pozwala na przekazanie pustego wskaźnika.strtok()
używa pustego wskaźnika dla kolejnych wywołań.Jeśli weźmiemy przypadek za
snprintf()
, sensowne jest zezwolenie na przekazanie wskaźnika zerowego, gdy „n” jest równe zero, ale nie ma to miejsca w przypadku innych funkcji (biblioteki standardowej), które dopuszczają podobne zero „n”. Na przykład:memcpy()
,memmove()
,strncpy()
,memset()
,memcmp()
.Jest to określone nie tylko we wstępie do biblioteki standardowej, ale także raz jeszcze we wstępie do tych funkcji:
C99 §7.21.1 p2
/C11 §7.24.1 p2
Czy to jest zamierzone?
Nie wiem, czy UB
%p
ze wskaźnikiem zerowym jest w rzeczywistości celowe, ale ponieważ standard wyraźnie stwierdza, że wskaźniki zerowe są uważane za nieprawidłowe wartości jako argumenty funkcji biblioteki standardowej, a następnie idzie i wyraźnie określa przypadki, w których wartość null wskaźnik jest ważny argument (snprintf, wolna, itd), a następnie idzie i po raz kolejny powtarza wymóg argumenty ważność nawet w zera „n” przypadki (memcpy
,memmove
,memset
), to myślę, że to rozsądne założenie, że Komisja normalizacyjna C nie przejmuje się zbytnio niezdefiniowaniem takich rzeczy.źródło
%p
nie powinno być niezdefiniowanym zachowaniemAutorzy standardu C nie starali się wyczerpująco wyszczególnić wszystkich wymagań behawioralnych, które implementacja musi spełnić, aby nadawała się do określonego celu. Zamiast tego spodziewali się, że ludzie piszący kompilatory wykażą się pewnym zdrowym rozsądkiem, niezależnie od tego, czy standard tego wymaga, czy nie.
Pytanie, czy coś wywołuje UB, rzadko jest samo w sobie użyteczne. Prawdziwe ważne kwestie to:
Czy ktoś, kto próbuje napisać wysokiej jakości kompilator, powinien zachowywać się w przewidywalny sposób? W przypadku opisywanego scenariusza odpowiedź brzmi zdecydowanie tak.
Czy programiści powinni mieć prawo oczekiwać, że wysokiej jakości kompilatory dla wszystkiego, co przypomina zwykłe platformy, będą zachowywać się w przewidywalny sposób? W opisanym scenariuszu powiedziałbym, że tak.
Czy niektórzy tępi autorzy kompilatorów mogą rozciągnąć interpretację standardu, aby usprawiedliwić zrobienie czegoś dziwnego? Mam nadzieję, że nie, ale nie wykluczam tego.
Czy kompilatory dezynfekujące powinny wrzeszczeć o tym zachowaniu? To zależałoby od poziomu paranoi ich użytkowników; kompilator czyszczący prawdopodobnie nie powinien domyślnie skrzeczać o takim zachowaniu, ale być może zapewniać opcję konfiguracji na wypadek, gdyby programy mogły zostać przeniesione do "sprytnych" / głupich kompilatorów, które zachowują się dziwnie.
Jeśli rozsądna interpretacja normy oznaczałaby, że zdefiniowano zachowanie, ale niektórzy autorzy kompilatorów rozciągają tę interpretację, aby uzasadnić postępowanie w inny sposób, czy to naprawdę ma znaczenie, co mówi standard?
źródło
printf("%p", (void*) 0)
nieokreślone zachowanie jest zgodne ze standardem? Głęboko zagnieżdżone wywołania funkcji są tak samo istotne jak cena herbaty w Chinach. I tak, UB jest bardzo powszechne w programach w świecie rzeczywistym - co z tego?%p
każdego możliwego znaczenia pytania.