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

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

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

a[i] = i++;

Уверены ли вы. Это выглядит хорошо определенным.

Martin York 15.12.2008 10:10

6.2.2 Порядок оценки [expr.evaluation] в языке программирования C++ так сказано. У меня нет других ссылок

yesraaj 15.12.2008 10:24

Он прав ... только что посмотрел на 6.2.2 на языке программирования C++, и там сказано, что v [i] = i ++ не определено.

dancavallaro 15.12.2008 10:24

Я могу представить, потому что компилятор выполняет i ++ до или после вычисления местоположения в памяти v [i]. конечно, я всегда буду там назначен. но он мог писать либо в v [i], либо в v [i + 1] в зависимости от порядка операций ..

Evan Teran 15.12.2008 10:44

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

Rob Kennedy 15.12.2008 10:47

@ Роб: разве я не это только что сказал?

Evan Teran 15.12.2008 10:49

В качестве примера: он может выглядеть следующим образом: (psuedo asm): "lea edi, [v + i]; mov [edi], i; inc i" или "mov eax, i; inc i; lea edi, [v + i]; mov [edi], eax "- Эван Теран

Evan Teran 15.12.2008 11:05

Все, что говорит язык программирования C++, это «Порядок операций над частями выражения внутри выражения не определен. В частности, вы не можете предполагать, что выражение оценивается слева направо».

dancavallaro 15.12.2008 11:14

В ПОРЯДКЕ. После прочтения n2521 Раздел 5.2.6 думаю, что он у меня есть. Смотрите мой ответ ниже.

Martin York 15.12.2008 11:46

В наши дни для модератора кажется обрядом принудительного закрытия популярные вопросы, даже те (такие как этот), относящиеся к серой зоне (и поэтому решение должно приниматься сообществом в целом, а не одним чрезмерно усердным модератором).

BlueRaja - Danny Pflughoeft 27.07.2013 01:21

Собственно, это кажется мне прекрасным примером хорошего вклада в SO.

Kristian D'Amato 27.10.2013 01:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
201
11
70 088
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

Разве не для этого нужен <cstdint>? Он определяет такие типы, как uint16_6 и так далее.

Jasper Bekkers 15.12.2008 10:54

Да, но размер большинства типов, скажем, длинных, точно не определен.

JaredPar 15.12.2008 11:04

также cstdint еще не является частью текущего стандарта C++. см. boost / stdint.hpp для получения переносимого в настоящее время решения.

Evan Teran 15.12.2008 11:07

Это не неопределенное поведение. В стандарте говорится, что соответствующая платформа определяет размеры, а не стандарт, определяющий их.

Daniel Earwicker 15.12.2008 11:16

Также не то, что стандарт не определяет, сколько 1 байт. Это как минимум 8 бит, но все, что разрешено выше, поэтому байт C++ не обязательно равен байту реальной жизни / в любом случае, я голосую за вас, поскольку это не заслуживает отрицательного голосования.

Sebastian Mach 02.02.2010 11:37

@JaredPar: Это не совсем так. long - это гарантированно будет не менее 32 бит.

John Dibling 26.06.2012 20:23

@JohnDibling Я просмотрел этот пост и связанные со ссылками, но не смог найти точного источника, поскольку это 32 бита. Один пользователь заявил об этом, но не было ссылки на спецификацию, которая сделала бы

JaredPar 29.06.2012 21:57

@JaredPar: Это сложный пост с множеством обсуждений, поэтому я подвел итог здесь. Суть в следующем: «5. Чтобы представить -2147483647 и +2147483647 в двоичном формате, вам нужно 32 бита».

John Dibling 29.06.2012 22:17

По схожим причинам int имеет не менее 16 бит, а long long - не менее 64 (добавлен к стандарту в C++ 11).

David Stone 21.02.2013 21:26
Ответ принят как подходящий

Указатель

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

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

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

Целочисленные переполнения

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

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

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

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

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

ООП

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

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

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

Быть классифицированным

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

Хм ... NaN (x / 0) и Infinity (0/0) были охвачены IEEE 754, если C++ был разработан позже, почему он записывает x / 0 как undefined?

new123456 05.04.2011 17:22

Re: «Обратная косая черта, за которой следует символ, который не является частью указанных escape-кодов в символьной или строковой константе». Это UB в C89 (§3.1.3.4) и C++ 03 (который включает C89), но не в C99. C99 говорит, что «результат не является токеном и требуется диагностика» (§6.4.4.4). Предположительно C++ 0x (который включает C89) будет таким же.

Adam Rosenfield 07.06.2011 08:31

В стандарте C99 есть список неопределенного поведения в приложении J.2. Чтобы адаптировать этот список к C++, потребуется некоторая работа. Вам нужно будет изменить ссылки на правильные предложения C++, а не на предложения C99, удалить все, что не имеет отношения к делу, а также проверить, действительно ли все эти вещи не определены в C++, а также в C. Но это дает начало.

Steve Jessop 07.06.2011 12:13

Довольно полезный и внушительный список. Мне нужно добавить это, так как он меня просто обжег: размер перечисления не определен; он должен быть достаточно большим, чтобы содержать int. «Каждый перечислимый тип должен быть совместим с char, целочисленным типом со знаком или целочисленным типом без знака. Выбор типа определяется реализацией, но должен быть способен представлять значения всех членов перечисления».

Lolo 09.08.2012 19:30

@ new123456 - не все модули с плавающей запятой совместимы с IEE754. Если C++ требует соответствия IEE754, компиляторам необходимо будет протестировать и обработать случай, когда RHS равен нулю, с помощью явной проверки. Сделав поведение неопределенным, компилятор может избежать этих накладных расходов, сказав: «Если вы используете FPU, отличный от IEE754, вы не получите поведения IEEE754 FPU».

SecurityMatt 01.09.2012 18:18

«Сдвиг значений на величину, превышающую размер log2 контейнера (например, __int64 i = (37 << 72) не определено)». Это немного вводит в заблуждение и не всегда соответствует действительности. Это неопределенное поведение только в том случае, если значение sizeof(int) * CHAR_BIT <= 72 истинно. Объект, в который вы помещаете результат 37 << 72, не имеет значения, само выражение вызывает неопределенное поведение. Например, uint8_t n = (1 << 15); вполне подойдет. Сначала вы оцените 1 << 15, который равен 2 ** 15. Результатом является затем неявное преобразование из int в uint8_t, что означает, что n == 0 (из-за модульной арифметики)

David Stone 21.02.2013 21:00

«Вычисление выражения, результат которого не входит в диапазон соответствующих типов» .... целочисленное переполнение хорошо определено для целочисленных типов UNSIGNED, но только не со знаком.

nacitar sevaht 10.08.2013 00:06

Я как раз собирался сделать тот же комментарий, что и @nacitarsevaht.

user541686 20.08.2013 02:07

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

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


Этот:

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

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

Порядок не указан, не определен.

Rob Kennedy 15.12.2008 10:55

Ненавижу это :) Я потерял день работы, разыскивая один из этих случаев ... в любом случае выучил урок и, к счастью, больше не упал

Robert Gould 15.12.2008 11:29

@Rob: Я бы поспорил с вами об изменении значения здесь, но я знаю, что комитет по стандартам очень требователен к точному определению этих двух слов. Так что я просто изменю :-)

Martin York 15.12.2008 11:34

Мне повезло с этим. Меня это укусило, когда я учился в колледже, и у меня был профессор, который взглянул на это и рассказал мне о моей проблеме примерно за 5 секунд. Неизвестно, сколько времени я бы потратил на отладку в противном случае.

Bill the Lizard 15.12.2008 19:06

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

int i =1;
i = ++i;

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

Infact по меньшей мере один раз между двумя точками последовательности.

Prasoon Saurav 12.08.2010 17:10

@Prasoon: Я думаю, вы имели в виду: в большинстве один раз между двумя точками последовательности. :-)

Nawaz 07.06.2011 13:24

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

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

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.

что подразумевается под точкой последовательности?

yesraaj 15.12.2008 11:25
en.wikipedia.org/wiki/Sequence_point
Martin York 15.12.2008 11:36

Ох ... это мерзко, тем более, что я видел ту точную структуру, рекомендованную в Java

Tom 15.12.2008 17:44

Обратите внимание, что некоторые компиляторы действительно определяют поведение в этой ситуации. В VC++ 2005+, например, если a является изменчивым, необходимые барьеры памяти устанавливаются для предотвращения переупорядочения инструкций, чтобы сработала блокировка с двойной проверкой.

Eclipse 23.06.2009 20:11

Мартин Йорк: <i> // (c) гарантированно произойдет после (a) и (b) </i> Это так? По общему признанию, в этом конкретном примере единственный сценарий, в котором это могло иметь значение, был бы, если бы 'i' был изменчивой переменной, отображаемой в аппаратный регистр, а [i] (старое значение 'i') было бы присвоено ей псевдонимом, но есть ли какие-либо гарантировать, что приращение произойдет до точки последовательности?

supercat 15.08.2010 00:18

@supercat: Да, это гарантировано. Обе стороны = должны быть оценены, прежде чем его можно будет оценить.

Martin York 12.10.2010 18:22

@Martin York: выражение «i = i ++;» обычно приводится как пример неопределенного поведения в C (я знаю, что поведение определено в некоторых других языках, таких как Java). Если побочные эффекты приращения не гарантированно исчезнут и будут устранены к тому времени, когда появится «i = i ++;» происходит присвоение, почему они должны быть гарантированно завершены к моменту "a [i] = i ++;" имеет место?

supercat 12.10.2010 19:13

@supercat: Хорошо, я понимаю, откуда вы. Вы правы, эффект от оператора ++ (на i) не гарантируется к этому моменту.

Martin York 12.10.2010 19:32
a[i] = i++; в любом случае является неопределенным поведением. Компилятор может делать больше, чем просто переупорядочивать его, он может игнорировать это или делать что-то с носом. i модифицируется на RHS, а на LHS он читается иначе, чем с целью определения записываемого значения. Игра окончена (если i не является типом класса с перегрузкой operator++, тогда любые модификации объекта внутри этой перегрузки надежно обертываются в точках последовательности, и это просто неуказанный порядок, а не UB).
Steve Jessop 07.06.2011 12:18

@ Стив: Совершенно верно. Я просто пытался объяснить, почему он должен быть неопределенным (и некоторые возможные объяснения реализации).

Martin York 07.06.2011 18:23

@Martin: не уверен, что согласен с тем, что это объясняет, почему он должен быть неопределенным. Бывают и другие ситуации, в которых порядок операций не указан без вызова UB. Например, в случае, о котором я упоминал, где i - это тип класса, имитирующий целое число с подходящими operator++(int) и operator int(), ваши два варианта «или / или» все еще возможны, но нет UB. Тем не менее, возможно, говоря «неопределенное поведение», спрашивающий имеет в виду любую ситуацию, в которой поведение не полностью закреплено, в отличие от стандартного определения UB.

Steve Jessop 07.06.2011 20:22

Ваш пример блокировки с двойной проверкой - беспощадный. ಥ_ಥ

Brian Gordon 25.09.2011 03:30

Привет, я бродил, какие еще проблемы для второй версии реализации синглтона. Вроде идеально.

Joey.Z 20.09.2013 19:02

@zoujyjs: Если вы хотите создать синглтон с указателями, вы делаете это неправильно. См. Шаблон проектирования C++ Singleton

Martin York 20.09.2013 19:16

Да, я читал об этом также в пункте 04 «Эффективный C++». И это гарантированно поточно-ориентированное после C++ 0x. Но почему не указатели? А если нет, что если я просто верну разыменование указателей? Поскольку синглтон должен присутствовать на протяжении всей жизни процесса, мне не нужно было уничтожать его с помощью явного указателя.

Joey.Z 20.09.2013 19:31

В частности, я хочу знать, считается ли приведенный выше код достаточно хорошей реализацией DCLP.

Joey.Z 20.09.2013 19:32

@zoujyjs: Нет, это не хорошо в C++ 03. В C++ есть проблемы с потоковой передачей и блокировкой с двойной проверкой, что делает невозможным правильное выполнение; на нем есть знаменитая статья Саттера. Я считаю, что это исправлено для C++ 11, но вам нужно будет использовать новые функции, не описанные здесь.

Martin York 20.09.2013 22:39

@zoujyjs: Вот оно: C++ и опасности двойной проверки блокировки На самом деле Мейерс и Александреску

Martin York 20.09.2013 22:49

Присвоение константе после удаления constness с помощью const_cast<>:

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

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

Сделал это раньше, но я не понимаю, как это не определено. Совершенно очевидно, что вы делаете бесконечную рекурсию запоздало.

Robert Gould 15.12.2008 11:25

Проблема в том, что компилятор не может проверить ваш код и точно решить, будет ли он страдать от бесконечной рекурсии или нет. Это пример проблемы с остановкой. См .: stackoverflow.com/questions/235984/…

Daniel Earwicker 15.12.2008 12:17

Да, это определенно проблема с остановкой

Robert Gould 16.12.2008 04:44

это привело к сбою моей системы из-за подкачки, вызванной слишком маленьким объемом памяти.

Johannes Schaub - litb 28.12.2008 14:13

Константы препроцессора, которые не помещаются в int, также являются временем компиляции.

Joshua 18.08.2010 01:01

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

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

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

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

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

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

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

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

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

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

Если вы достигнете этих пределов, у вас будет больше проблем, чем неопределенное поведение.

new123456 22.04.2011 06:00

Вы можете ЛЕГКО превысить 65535 байт в объекте, таком как STD :: vector

Demi 13.09.2013 08:15

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

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

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

7.21.2.1 Функция memcpy

Synopsis

1/ #include void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

Description

2/ The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined. Returns 3 The memcpy function returns the value of s1.

7.21.2.2 Функция memmove

Synopsis

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

Description

2 The memmove function copies n characters from the object pointed to by s2 into the object pointed to by s1. Copying takes place as if the n characters from the object pointed to by s2 are first copied into a temporary array of n characters that does not overlap the objects pointed to by s1 and s2, and then the n characters from the temporary array are copied into the object pointed to by s1. Returns

3 The memmove function returns the value of s1.

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