FANDOM


Undefined behavior, czyli zachowanie niezdefiniowane, oznacza, że program został napisany źle (lub dostarczono mu dane, na których działa źle) i może wydarzyć się cokolwiek, bo pozwala na to definicja języka. Może to być wyjątek, może to być zamknięcie aplikacji przez system operacyjny. Największe niebezpieczeństwo polega na tym, że zachowanie niezdefiniowane nie musi za każdym razem objawiać się tak samo - czasem może wyglądać, że program działa poprawnie, a kompilatory nie muszą ostrzegać o wystąpieniu takiego przypadku.

Bronek Kozicki to trafnie opisał w wątku delete i typ, że program powodujący niezdefiniowane zachowanie:

zrobi cokolwiek, może np. rozlać kawę na Twój komputer albo spowodować wykolejenie tramwaju.

Najczęstsze przypadki undefined behaviorEdytuj

Te są zazwyczaj dobrze znane i raczej unikane.

dostęp poza zaalokowany obszarEdytuj

char t[10];
char x = t[10];

inny przykład

char *p = new char[10];
char x = p[10];

użycie niezainicjowanego wskaźnikaEdytuj

Można to traktować jako bardziej "zaowalowaną" wersję dostępu poza zaalokowany obszar. Ale tu jeszcze lepiej widać "niezdefiniowanie" zachowania - przypadkiem wartość wskaźnika może być poprawna (wskazywać na istniejący obiekt), ale to czysty przypadek. Zazwyczaj tak nie będzie...

char *p;
char x = *p; // nie wiadomo na co wskazuje p

Szczególnym przypadkiem jest odwołanie się się do adresu zmiennej lokalnej po wyjściu z funkcji.

dereferencja pustego wskaźnikaEdytuj

char *p = 0;
char x = *p;

wywołanie delete na zwolnionym wskaźnikuEdytuj

char *p = new char;
delete p; // tu jest dobrze
delete p; // a tu już może zdarzyć się cokolwiek

mieszanie new z delete[] i new[] z deleteEdytuj

Częściej występuje to drugie:

char* p = new char[10];
delete p; // powinno być delete []p;

odwołanie się się do adresu zmiennej lokalnej po wyjściu z funkcjiEdytuj

char* pobierz_napis()
{
   char napis[100];
   strcpy(napis, "napis");
   return napis;
}

char *p = pobierz_napis();
printf("%s\n", p); // zmiennej napis wskazywanej przez p już dawno nie ma!

dzielenie przez zeroEdytuj

Zazwyczaj zgłaszane jako wyjątek, ale niekoniecznie.

Mniej znane, a bardziej zaskakująceEdytuj

Co może być zaskakujące, do tej samej kategorii należy również wiele zachowań, które "przechodzą" i dają zgodne ze "zdrowym rozsądkiem" zachowania... na najpopularniejszej platformie, czyli 32-bitowej x86. Jednak wystarczy trochę "egzotyki" i zaczynają się schody.

nadużycia rzutowania i arytmetyki na wskaźnikachEdytuj

Użycie wskaźnika po rzutowaniu na inny typ, wykonaniu operacji arytmetyki na wskaźniku i rzutowaniu z powrotem - częsta "sztuczka", które doskonale działa (choć nieco spowalnia wykonanie programu) na platformie x86, ale na innych może powodować np. wyjątek z zamknięciem aplikacji włącznie.

int ti[10];

int *pi1 = ti + 1;
int x1 = *pi; // tak oczywiście można

char *pc2 = (char*)ti;
pc2 += 3;
int x = *((int*)pc2); // ale to jest niezdefiniowane zachowanie

Tu z kolei barierą "fizyczną" jest brak w procesorze instrukcji dostępu do danych nie leżących na granicy słowa maszynowego (najczęściej generowanie wyjątku procesora przy próbie dostępu pod niewyrównany adres). O ile kompilator "wie" jakim typem wskaźnika operuje, potrafi wygenerować odpowiednią sekwencję dostępu do dowolnie położonej danej - pobiera np. dwa słowa i za pomocą operacji bitowych "składa" żądaną daną. Jeśli pozbawimy kompilator informacji o pierwotnym typie wskaźnika i dodatkowo "zamieszamy" mu z arytmetyką - kompilator nie ma szans. Na platformie x86 takie "sztuczki" wyglądają na poprawne, bo o odpowiedni dostęp do niewyrównanej danej troszczy się sam procesor.

wielokrotna modyfikacja jednej zmiennej w wyrażeniuEdytuj

f(++a, ++a);

Wyjaśnienie w tym wątku, również w FAQ.

przesuwanie binarne o więcej bitów, niż długość argumentuEdytuj

Również w przypadku ujemnej wartości przesunięcia:

int x = 1; // zakładam 32-bitowy int
int y = x << 33;

Dla większości kompilatorów na platformie x86 wynik jest akurat w miarę deterministyczny: dla procesorów nowszych niż 8086/88 będzie to przesunięcie o 1 (x << 1 - rozkaz shr eax, n używa tylko 5 najmłodszych bitów z n), dla 8086/88 - wyzerowanie ("wysunięcie" wszystkich bitów). Ale nadal w ogólności jest to undefined behavior... Odpowiedź z odpowiednim cytatem na angielskojęzycznej grupie.

Zbliżonym problemem jest:

przekroczenie zakresu zmiennej ze znakiem jako wynik operacji arytmetycznejEdytuj

int x = 0x7FFFFFFF; // zakładam 32-bitowy int
int y = x + 2;

Dla większości platform wynikiem powyższej operacji będzie overflow, ale nie jest to zachowanie gwarantowane przez standard C++ . Problem nie dotyczy wbudowanych typów bez znaku:

unsigned int x = 0xFFFFFFFFu; // zakładam 32-bitowy unsigned int
unsigned int y = x + 2u; // OK, gwarantowany overflow

niezgodność typów formatowania i parametrów funkcji printfEdytuj

int n = 3;
printf("wynik %.2f\n", n);

Przykład zaczerpnięty z tego wątku, tam również wyjaśnienie.

Niektóre kompilatory (na pewno gcc) potrafi czasem ostrzec o niezgodności typów w formacie, z tymi z listy argumentów.

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.

Więcej z Fandomu

Losowa wiki