Niezdefiniowana wartość
W informatyce (szczególnie w programowaniu ) niezdefiniowana wartość to warunek, w którym wyrażenie nie ma poprawnej wartości , chociaż jest poprawne składniowo . Niezdefiniowanej wartości nie należy mylić z pustym łańcuchem , boolowskim „fałszem” ani innymi „pustymi” (ale zdefiniowanymi) wartościami. W zależności od okoliczności ocena do niezdefiniowanej wartości może prowadzić do wyjątku lub niezdefiniowanego zachowania , ale w niektórych językach programowania niezdefiniowane wartości mogą wystąpić podczas normalnego, przewidywalnego przebiegu wykonywania programu .
Języki o typie dynamicznym zwykle traktują niezdefiniowane wartości jawnie, jeśli to możliwe. Na przykład Perl ma operatora undef
, który może „przypisać” taką wartość zmiennej. W innych systemach typu niezdefiniowana wartość może oznaczać nieznaną, nieprzewidywalną wartość lub po prostu błąd programu przy próbie jej oszacowania. Typy dopuszczające wartość null oferują podejście pośrednie; patrz poniżej .
Obsługiwanie
Wartość funkcji częściowej jest niezdefiniowana, gdy jej argument jest poza dziedziną definicji . Obejmuje to liczne arytmetyczne , takie jak dzielenie przez zero , pierwiastek kwadratowy lub logarytm z liczby ujemnej itp. Innym typowym przykładem jest dostęp do tablicy z indeksem, który jest poza zakresem, podobnie jak wartość w tablicy asocjacyjnej dla klucza, który nie zawiera. Istnieją różne sposoby radzenia sobie z takimi sytuacjami w praktyce:
Zarezerwowana wartość
W aplikacjach, w których niezdefiniowane wartości muszą być obsługiwane z wdziękiem, często rezerwuje się specjalną wartość pustą, którą można odróżnić od normalnych wartości. To rozwiązuje problem, tworząc zdefiniowaną wartość reprezentującą wcześniej niezdefiniowany przypadek. Jest na to wiele przykładów:
-
Standardowa biblioteka wejść/wyjść C rezerwuje specjalną wartość
EOF
, aby wskazać, że nie ma więcej dostępnych wejść. Funkcja getchar()
zwraca następny dostępny znak wejściowy lubEOF ,
jeśli nie ma więcej dostępnych znaków. ( ASCII definiuje w tym celu znak zerowy , ale standardowa biblioteka I/O chce mieć możliwość wysyłania i odbierania znaków zerowych, więc definiuje oddzielną wartośćEOF
.) - Standard arytmetyki zmiennoprzecinkowej IEEE 754 definiuje specjalną wartość „ niebędącą liczbą ”, która jest zwracana, gdy operacja arytmetyczna nie ma zdefiniowanej wartości. Przykładami są dzielenie przez zero , pierwiastek kwadratowy lub logarytm liczby ujemnej .
-
Structured Query Language ma specjalną wartość
NULL
, aby wskazać brakujące dane. - Język Perl pozwala sprawdzić zdefiniowaność wyrażenia za pomocą predykatu
define()
. - Wiele języków programowania obsługuje koncepcję wskaźnika zerowego różniącego się od dowolnego prawidłowego wskaźnika i często używanego jako powrót błędu.
- Niektóre języki pozwalają większości typów na dopuszczanie wartości null, na przykład C# .
- Większość wywołań systemowych systemu Unix zwraca specjalną wartość -1, aby wskazać niepowodzenie.
Podczas gdy języki o typie dynamicznym często zapewniają, że niezainicjowane zmienne mają domyślnie wartość pustą, wartości o typie statycznym często tego nie robią i odróżniają wartości zerowe (które są dobrze zdefiniowane) od wartości niezainicjowanych (które nie są).
Obsługa wyjątków
Niektóre języki programowania mają koncepcję obsługi wyjątków w przypadku niepowodzenia zwrócenia wartości. Funkcja zwraca w określony sposób, ale nie zwraca wartości, więc nie ma potrzeby wymyślania specjalnej wartości zwracanej.
Odmianą tego jest obsługa sygnałów , która odbywa się na poziomie systemu operacyjnego i nie jest zintegrowana z językiem programowania. Programy obsługi sygnałów mogą próbować niektórych form odzyskiwania, takich jak zakończenie części obliczeń, ale bez takiej elastyczności, jak w pełni zintegrowana obsługa wyjątków.
Funkcje niezwracające
Funkcja, która nigdy nie zwraca, ma niezdefiniowaną wartość, ponieważ nigdy nie można jej zaobserwować. Takim funkcjom formalnie przypisuje się typ dolny , który nie ma żadnych wartości. Przykłady dzielą się na dwie kategorie:
- Funkcje, które zapętlają się w nieskończoność . Może to powstać celowo lub w wyniku poszukiwania czegoś, czego nigdy nie można znaleźć. (Na przykład w przypadku nieudanego operatora μ w częściowej funkcji rekurencyjnej ).
- Funkcje, które kończą obliczenia, takie jak
wyjście
wywołania systemowego . Z poziomu programu jest to nie do odróżnienia od poprzedniego przypadku, ale robi różnicę dla osoby wywołującej program.
Niezdefiniowane zachowanie
Wszystkie powyższe metody obsługi niezdefiniowanych wartości wymagają wykrycia niezdefiniowaności. Oznacza to, że wywoływana funkcja stwierdza, że nie może zwrócić normalnego wyniku i podejmuje pewne działania, aby powiadomić wywołującego. Na drugim końcu spektrum niezdefiniowane zachowanie nakłada na wywołującego obowiązek unikania wywoływania funkcji z argumentami spoza jej domeny. Nie ma ograniczeń co do tego, co może się wydarzyć. W najlepszym przypadku łatwo wykrywalna awaria ; w najgorszym przypadku subtelny błąd w pozornie niepowiązanych obliczeniach.
(Formalna definicja „niezdefiniowanego zachowania” obejmuje jeszcze bardziej ekstremalne możliwości, w tym rzeczy takie jak „ zatrzymanie się i podpalenie ” oraz „sprawienie, by demony wyleciały ci z nosa”.)
Klasycznym przykładem jest zwisające odwołanie do wskaźnika . Wyłuskanie poprawnego wskaźnika jest bardzo szybkie , ale ustalenie, czy wskaźnik jest prawidłowy, może być bardzo skomplikowane. Dlatego sprzęt komputerowy i języki niskiego poziomu, takie jak C , nie próbują sprawdzać poprawności wskaźników przed ich dereferencją, zamiast tego przekazują odpowiedzialność programiście. Daje to szybkość kosztem bezpieczeństwa.
Wartość nieokreślona sensu stricto
Ścisła definicja niezdefiniowanej wartości jest powierzchownie poprawnym (niezerowym) wyjściem, które jest bez znaczenia, ale nie wyzwala niezdefiniowanego zachowania. Na przykład przekazanie liczby ujemnej do szybkiego odwrotnego pierwiastka kwadratowego da liczbę. Niezbyt użyteczna liczba, ale obliczenia zostaną zakończone i coś zwrócą .
Niezdefiniowane wartości występują szczególnie często w sprzęcie. Jeśli przewód nie przenosi przydatnych informacji, nadal istnieje i ma pewien poziom napięcia. Napięcie nie powinno być nienormalne (np. szkodliwe przepięcie ), ale konkretny poziom logiczny nie ma znaczenia.
Ta sama sytuacja ma miejsce w oprogramowaniu, gdy bufor danych jest zapewniony, ale nie do końca wypełniony. Na przykład funkcja strftime
biblioteki C konwertuje znacznik czasu do postaci czytelnej dla człowieka w dostarczonym buforze wyjściowym. Jeśli bufor wyjściowy nie jest wystarczająco duży, aby pomieścić wynik, zwracany jest błąd, a zawartość bufora jest niezdefiniowana.
Z drugiej strony, otwarte
wywołanie systemowe w POSIX przyjmuje trzy argumenty: nazwę pliku, niektóre flagi i tryb pliku. Tryb pliku jest używany tylko wtedy, gdy flagi zawierają O_CREAT
. Powszechne jest używanie dwuargumentowej formy open
, która zapewnia niezdefiniowaną wartość dla trybu pliku, gdy pominięto O_CREAT .
Czasami warto pracować z takimi niezdefiniowanymi wartościami w ograniczony sposób. Ogólne obliczenie może być nadal dobrze zdefiniowane, jeśli niezdefiniowana wartość zostanie później zignorowana.
Jako przykład tego, język C pozwala na konwersję wskaźnika na liczbę całkowitą, chociaż wartość liczbowa tej liczby całkowitej jest niezdefiniowana. Nadal może być przydatny do debugowania, porównywania dwóch wskaźników w celu uzyskania równości lub tworzenia połączonej listy XOR .
Bezpieczna obsługa niezdefiniowanych wartości jest ważna w optymistycznych systemach kontroli współbieżności, które wykrywają warunki wyścigu po fakcie. Na przykład odczytanie wspólnej zmiennej chronionej przez seqlock da niezdefiniowaną wartość przed stwierdzeniem, że doszło do sytuacji wyścigu. Następnie odrzuci niezdefiniowane dane i spróbuje ponownie wykonać operację. Daje to określony wynik, o ile operacje wykonywane na niezdefiniowanych wartościach nie powodują pełnego niezdefiniowanego zachowania.
Innymi przykładami użytecznych niezdefiniowanych wartości są generatory liczb losowych i funkcje mieszające . Konkretne zwracane wartości są niezdefiniowane, ale mają dobrze zdefiniowane właściwości i mogą być używane bez błędów.
Notacja
W teorii obliczalności nieokreśloność wyrażenia oznacza się jako wyrażenie ↑, a określoność jako wyrażenie ↓.
Zobacz też
- Zdefiniowane i niezdefiniowane (matematyka)
- Null (SQL)