Які загальні невизначені форми поведінки, про які повинен знати програміст на C ++?
Скажіть, як:
a[i] = i++;
Які загальні невизначені форми поведінки, про які повинен знати програміст на C ++?
Скажіть, як:
a[i] = i++;
Відповіді:
NULL
покажчикаmemcpy
для копіювання буферів, що перекриваються .int64_t i = 1; i <<= 72
не визначено)int i; i++; cout << i;
)volatile
або sig_atomic_t
при отриманні сигналуlong int
#if
виразіПорядок оцінювання параметрів функції - це не визначена поведінка . (Це не призведе до збою, вибуху або замовлення піци ... На відміну від невизначеної поведінки .)
Єдина вимога - всі параметри повинні бути повністю оцінені до виклику функції.
Це:
// The simple obvious one.
callFunc(getA(),getB());
Це може бути рівнозначно цьому:
int a = getA();
int b = getB();
callFunc(a,b);
Або це:
int b = getB();
int a = getA();
callFunc(a,b);
Це може бути будь-який; це залежить від компілятора. Результат може мати значення, залежно від побічних ефектів.
Компілятор вільний повторно упорядкувати частини вираження (якщо вважати, що значення не змінюється).
З початкового питання:
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;
Подвійне перевірене блокування. І одна проста помилка зробити.
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.
Моя улюблена - "Нескінченна рекурсія в інстанціюванні шаблонів", тому що я вважаю, що це єдиний, де не визначена поведінка відбувається під час компіляції.
Окрім невизначеної поведінки , існує також однаково неприємна поведінка, визначена реалізацією .
Невизначена поведінка виникає, коли програма робить щось, результат якого не визначено стандартом.
Поведінка, визначена реалізацією, - це дія програми, результат якої не визначений стандартом, але яку необхідно документувати. Приклад - "Багатобайтові літерали символів", із запитання про переповнення стека. Чи є компілятор C, який цього не зможе скомпілювати? .
Поведінка, визначена реалізацією, кусає вас лише коли ви починаєте перенесення (але оновлення до нової версії компілятора також переносить!)
Змінні можуть бути оновлені лише один раз у виразі (технічно один раз між точками послідовності).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Основне розуміння різних екологічних меж. Повний список знаходиться в розділі 5.2.4.1 специфікації C. Ось кілька;
Я насправді трохи здивувався межі 1023 міток справи для оператора перемикання, я можу передбачити перевищення цього для генерованого коду / lex / парсери досить легко.
Якщо ці межі перевищені, у вас є невизначена поведінка (збої, вади безпеки тощо).
Правильно, я знаю, це з специфікації C, але C ++ ділиться цими основними підтримками.
Використовується memcpy
для копіювання між областями пам'яті, що перекриваються. Наприклад:
char a[256] = {};
memcpy(a, a, sizeof(a));
Поведінка не визначена відповідно до Стандарту C, який поширюється на стандарт C ++ 03.
Конспект
1 / #include void * memcpy (void * обмежити s1, const void * обмежити s2, size_t n);
Опис
2 / Функція memcpy копіює n символів з об'єкта, на який вказує s2, в об'єкт, на який вказує s1. Якщо копіювання відбувається між об'єктами, які перекриваються, поведінка не визначена. Повертає 3 Функція memcpy повертає значення s1.
Конспект
1 #include void * memmove (void * s1, const void * s2, size_t n);
Опис
2 Функція пам'яті копіює n символів з об'єкта, на який вказує s2, в об'єкт, на який вказує s1. Копіювання відбувається так, ніби n символів з об’єкта, на які вказує s2, спочатку копіюються у тимчасовий масив з n символів, який не перекриває об'єкти, на які вказують s1 та s2, а потім n символів із тимчасового масиву копіюються в об’єкт, на який вказує s1. Повертається
3 Функція пам'яті повертає значення s1.
Єдиним типом, для якого С ++ гарантує розмір, є char
. А розмір - 1. Розмір усіх інших типів залежить від платформи.