Каковы все распространенные неопределенные поведения, о которых должен знать программист 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 метки падежа для оператора switch, я могу предвидеть, что его превышение для сгенерированного кода / lex / parsers довольно легко.
Если эти пределы превышены, у вас есть неопределенное поведение (сбои, недостатки безопасности и т. Д.).
Да, я знаю, что это из спецификации 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 Функция memmove копирует n символов из объекта, на который указывает s2, в объект, на который указывает s1. Копирование происходит так, как будто n символов из объекта, на который указывает s2, сначала копируются во временный массив из n символов, который не перекрывает объекты, на которые указывают s1 и s2, а затем n символов из временного массива копируются в объект, на который указывает s1. Возвращает
3 Функция memmove возвращает значение s1.
Единственный тип, для которого C ++ гарантирует размер, это char
. И размер равен 1. Размер всех других типов зависит от платформы.