Jakie są wszystkie typowe niezdefiniowane zachowania, o których powinien wiedzieć programista C ++?
Powiedz:
a[i] = i++;
Jakie są wszystkie typowe niezdefiniowane zachowania, o których powinien wiedzieć programista C ++?
Powiedz:
a[i] = i++;
Odpowiedzi:
NULL
wskaźnikamemcpy
do kopiowania nakładających się buforów .int64_t i = 1; i <<= 72
Jest niezdefiniowana)int i; i++; cout << i;
)volatile
lub sig_atomic_t
przy odbiorze sygnałulong int
#if
wyrażeniuKolejność oceny parametrów funkcji jest nieokreślonym zachowaniem . (Nie spowoduje to awarii programu, wybuchu ani zamawiania pizzy ... w przeciwieństwie do niezdefiniowanego zachowania ).
Jedynym wymaganiem jest to, że wszystkie parametry muszą zostać w pełni ocenione przed wywołaniem funkcji.
To:
// The simple obvious one.
callFunc(getA(),getB());
Może być równoważne z tym:
int a = getA();
int b = getB();
callFunc(a,b);
Albo to:
int b = getB();
int a = getA();
callFunc(a,b);
Może być albo; to zależy od kompilatora. Wynik może mieć znaczenie, w zależności od skutków ubocznych.
Kompilator może dowolnie zamawiać części wyrażenia ewaluacyjnego (zakładając, że znaczenie nie ulegnie zmianie).
Z pierwotnego pytania:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
Blokada podwójnie sprawdzona. I jeden łatwy do popełnienia błąd.
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
Moim ulubionym jest „nieskończona rekurencja w tworzeniu instancji szablonów”, ponieważ uważam, że jest to jedyny przypadek, w którym niezdefiniowane zachowanie występuje w czasie kompilacji.
Oprócz nieokreślonego zachowania istnieje również równie nieprzyjemne zachowanie zdefiniowane w implementacji .
Niezdefiniowane zachowanie występuje, gdy program robi coś, czego wynik nie jest określony przez standard.
Zachowanie zdefiniowane w implementacji jest działaniem programu, którego wynik nie jest zdefiniowany w standardzie, ale wdrożenie musi udokumentować. Przykładem są „Wielobajtowe literały znaków” z pytania Przepełnienie stosu Czy istnieje kompilator C, który tego nie skompilował? .
Zachowanie zdefiniowane w implementacji gryzie cię dopiero po rozpoczęciu przenoszenia (ale uaktualnianie do nowej wersji kompilatora również się przenosi!)
Zmienne mogą być aktualizowane tylko raz w wyrażeniu (technicznie raz między punktami sekwencji).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Podstawowe zrozumienie różnych ograniczeń środowiskowych. Pełna lista znajduje się w sekcji 5.2.4.1 specyfikacji C. Tu jest kilka;
Byłem trochę zaskoczony limitem 1023 etykiet przypadków dla instrukcji switch. Mogę zauważyć, że przekroczenie generowanego kodu / lex / parserów jest dość łatwe.
Jeśli limity te zostaną przekroczone, masz niezdefiniowane zachowanie (awarie, wady bezpieczeństwa itp.).
Tak, wiem, że pochodzi ze specyfikacji C, ale C ++ udostępnia te podstawowe funkcje obsługi.
Używanie memcpy
do kopiowania między nakładającymi się regionami pamięci. Na przykład:
char a[256] = {};
memcpy(a, a, sizeof(a));
Zachowanie jest niezdefiniowane zgodnie ze standardem C, który jest objęty standardem C ++ 03.
Streszczenie
1 / #include void * memcpy (void * ograniczenia s1, const void * ograniczenia s2, size_t n);
Opis
2 / Funkcja memcpy kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Jeśli kopiowanie odbywa się między nakładającymi się obiektami, zachowanie jest niezdefiniowane. Zwraca 3 Funkcja memcpy zwraca wartość s1.
Streszczenie
1 #include void * memmove (void * s1, const void * s2, size_t n);
Opis
2 Funkcja memmove kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Kopiowanie odbywa się tak, jakby n znaków z obiektu wskazanego przez s2 zostało najpierw skopiowanych do tymczasowej tablicy n znaków, która nie zachodzi na obiekty wskazane przez s1 i s2, a następnie n znaków z tablicy tymczasowej jest skopiowanych do obiekt wskazywany przez s1. Zwroty
3 Funkcja memmove zwraca wartość s1.
Jedynym typem, dla którego C ++ gwarantuje rozmiar, jest char
. Rozmiar to 1. Rozmiar wszystkich innych typów zależy od platformy.