Каковы все общие типы поведения undefined, о которых должен знать программист на С++?

Каковы все общие поведения undefined, о которых должен знать программист на С++?

Скажите, например:

a[i] = i++;

Ответы

Ответ 1

Указатель

  • Разыменование указателя NULL
  • Разыменование указателя, возвращаемого "новым" распределением нулевого размера
  • Использование указателей на объекты, срок жизни которых закончился (например, стек выделенных объектов или удаленных объектов)
  • Разыменование указателя, который еще не определенно инициализирован
  • Выполнение арифметики указателя, которая дает результат вне границ (выше или ниже) массива.
  • Отмена указателя в месте, расположенном за пределами массива.
  • Преобразование указателей в объекты несовместимых типов
  • Использование memcpy для копирования перекрывающихся буферов.

Переполнение буфера

  • Чтение или запись объекта или массива со смещением, которое является отрицательным или превышает размер этого объекта (переполнение стека/кучи)

Переполнение целых чисел

  • Подписанное целочисленное переполнение
  • Оценка выражения, которое не определено математически
  • Значения сдвига слева на отрицательную величину (сдвиги вправо на отрицательные суммы определяются реализацией)
  • Изменение значений на количество, большее или равное количеству бит в числе (например, int64_t i = 1; i <<= 72 равно undefined)

Типы, Cast и Const

  • Приведение числового значения в значение, которое не может быть представлено целевым типом (либо напрямую, либо через static_cast)
  • Использование автоматической переменной до того, как она будет определенно назначена (например, int i; i++; cout << i;)
  • Использование значения любого объекта типа, отличного от volatile или sig_atomic_t при получении сигнала
  • Попытка изменить строковый литерал или любой другой объект const во время его жизни
  • Конкатенация узкой с широким строковым литералом во время предварительной обработки

Функция и шаблон

  • Не возвращать значение из функции возврата значения (напрямую или путем выключения из блока try)
  • Несколько разных определений для одного и того же объекта (класс, шаблон, перечисление, встроенная функция, статическая функция-член и т.д.).
  • Бесконечная рекурсия при создании шаблонов
  • Вызов функции с использованием разных параметров или привязка к параметрам и привязке, которые функция определяет как использующие.

OOP

  • Каскадные разрушения объектов со статической продолжительностью хранения
  • Результат присвоения частично перекрывающимся объектам
  • Рекурсивный повторный ввод функции во время инициализации ее статических объектов
  • Вызов виртуальных функций на чистые виртуальные функции объекта из его конструктора или деструктора
  • Ссылаясь на нестатические элементы объектов, которые еще не были сконструированы или уже уничтожены

Исходный файл и предварительная обработка

  • Непустой исходный файл, который не заканчивается новой строкой, или заканчивается обратным слэшем (до С++ 11)
  • Обратная косая черта, сопровождаемая символом, который не является частью указанных escape-кодов в символьной или строковой константе (это определено в С++ 11).
  • Превышение пределов реализации (количество вложенных блоков, количество функций в программе, доступное пространство стека...)
  • Числовые значения препроцессора, которые не могут быть представлены long int
  • Директива предварительной обработки с левой стороны определения функции-макроса
  • Динамическое создание определенного токена в выражении #if

Классифицировать

  • Вызов выхода во время уничтожения программы со статической продолжительностью хранения

Ответ 2

Порядок оценки параметров функции - это неуказанное поведение. (Это не приведет к сбою, взрыву программы или заказу пиццы... в отличие от поведения undefined.)

Единственное требование состоит в том, чтобы все параметры должны быть полностью оценены до вызова функции.


Это:

// 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);

Это может быть либо; это до компилятора. Результат может иметь значение, в зависимости от побочных эффектов.

Ответ 3

Компилятор может свободно переопределять оценочные части выражения (при условии, что значение не изменяется).

Из исходного вопроса:

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.

Ответ 4

Назначение константы после удаления const ness с использованием const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

Ответ 5

Моим любимым является "Бесконечная рекурсия в создании шаблонов", потому что я считаю, что это единственный, где поведение undefined происходит во время компиляции.

Ответ 6

Помимо undefined поведения, существует также такое же неприятное поведение, определяемое реализацией.

undefined поведение происходит, когда программа выполняет что-то, результат которого не указан стандартом.

Поведение, определяемое реализацией, является действием программы, результат которой не определен стандартом, но который требуется для документирования. Например, "Многобайтовые символьные литералы", из вопроса "Переполнение стека" Есть ли компилятор C, который не может скомпилировать это?.

Поведение, определяемое реализацией, только укусит вас при запуске переноса (но обновление до новой версии компилятора также переносит!)

Ответ 7

Переменные могут обновляться только один раз в выражении (технически один раз между точками последовательности).

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.

Ответ 8

Основное понимание различных экологических ограничений. Полный список приведен в разделе 5.2.4.1 спецификации C. Вот несколько:

  • 127 параметров в одном определении функции
  • 127 аргументов в одном вызове функции
  • 127 параметров в одном макроопределении
  • 127 аргументов в одном вызове макроса
  • 4095 символов в логической строке источника
  • 4095 символов в символьной строке буквальный или широкий строковый литерал (после конкатенация)
  • 65535 байт в объект (только в размещенной среде)
  • 15 уровней для # включенных файлов
  • 1023 футляры для коммутатора (за исключением тех, которые anynested switch statements)

На самом деле я был немного удивлен предел 1023 case-ярлыков для оператора switch, я могу заметить, что это довольно простое сведение сгенерированного кода /lex/parsers.

Если эти лимиты превышены, у вас есть поведение undefined (сбои, недостатки безопасности и т.д.).

Правильно, я знаю, что это из спецификации C, но С++ разделяет эти основные поддержки.

Ответ 9

Единственным типом, для которого С++ гарантирует размер, является char. И размер равен 1. Размер всех других типов зависит от платформы.

Ответ 10

Объекты уровня пространства имен в разных единицах компиляции никогда не должны зависеть друг от друга для инициализации, потому что их порядок инициализации undefined.

Ответ 11

Использование memcpy для копирования между перекрывающимися областями памяти. Например:

char a[256] = {};
memcpy(a, a, sizeof(a));

Поведение undefined в соответствии со стандартом C, которое подпадает под стандарт С++ 03.

7.21.2.1 Функция memcpy

Сводка

1/#include void * memcpy (void * restrict s1, const void * ограничивать s2, size_t n);

Описание

2/Функция memcpy копирует n символов из объекта, на который указывает s2, в объект на которое указывает s1. Если копирование происходит между перекрываемыми объектами, поведение undefined. Возвращает 3 Функция memcpy возвращает значение s1.

7.21.2.2 Функция memmove

Сводка

1 #include void * memmove (void * s1, const void * s2, size_t п);

Описание

2 Функция memmove копирует n символов из объекта, на который указывает s2 в объект, на который указывает s1. Копирование происходит, как если бы n символов из объекта, на который указывает s2, сначала копируются в временный массив из n символов, которые не перекрывают объекты на которые указывают s1 и s2, а затем n символов из временного массив копируются в объект, на который указывает s1. Возвращает

3 Функция memmove возвращает значение s1.