Каковы все распространенные неопределенные модели поведения, о которых должен знать программист на C ++?[закрыто]

StackOverflow https://stackoverflow.com/questions/367633

Вопрос

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

Скажем, как:

a[i] = i++;

Это было полезно?

Решение

Указатель

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

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

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

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

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

Типы, приведение и константа

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

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

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

ООП

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

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

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

Подлежащий классификации

  • Вызов exit во время уничтожения программы со статическим сроком хранения

Другие советы

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

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


Это:

// 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.

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

Присвоение константе после удаления constиспользование const_cast<>:

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

Кроме того неопределенное поведение, есть также не менее неприятный поведение, определяемое реализацией.

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

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

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

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

int i =1;
i = ++i;

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

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

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

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

Если эти ограничения превышены, у вас происходит неопределенное поведение (сбои, недостатки безопасности и т.д.).

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

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

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

Поведение не определено в соответствии со стандартом C, который подпадает под действие стандарта C ++ 03.

7.21.2.1 Функция memcpy

Краткий обзор

1/ #включить void *memcpy(void * ограничить s1, const void * ограничить s2, размер_t n);

Описание

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

7.21.2.2 Функция memmove

Краткий обзор

1 #включить 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.Размер всех остальных типов зависит от платформы.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top