О каких типах неопределенного поведения должен знать программист на C++?
Скажите, например:
a[i] = i++;
6.2.2 Порядок оценки [expr.evaluation] в языке программирования C++ так сказано. У меня нет других ссылок
Он прав ... только что посмотрел на 6.2.2 на языке программирования C++, и там сказано, что v [i] = i ++ не определено.
Я могу представить, потому что компилятор выполняет i ++ до или после вычисления местоположения в памяти v [i]. конечно, я всегда буду там назначен. но он мог писать либо в v [i], либо в v [i + 1] в зависимости от порядка операций ..
Мартин, переменная обновится через некоторое время после того, как она будет прочитана справа. Это может быть до или после чтения переменной для оценки левой стороны.
@ Роб: разве я не это только что сказал?
В качестве примера: он может выглядеть следующим образом: (psuedo asm): "lea edi, [v + i]; mov [edi], i; inc i" или "mov eax, i; inc i; lea edi, [v + i]; mov [edi], eax "- Эван Теран
Все, что говорит язык программирования C++, это «Порядок операций над частями выражения внутри выражения не определен. В частности, вы не можете предполагать, что выражение оценивается слева направо».
В ПОРЯДКЕ. После прочтения n2521 Раздел 5.2.6 думаю, что он у меня есть. Смотрите мой ответ ниже.
В наши дни для модератора кажется обрядом принудительного закрытия популярные вопросы, даже те (такие как этот), относящиеся к серой зоне (и поэтому решение должно приниматься сообществом в целом, а не одним чрезмерно усердным модератором).
Собственно, это кажется мне прекрасным примером хорошего вклада в SO.





Единственный тип, для которого C++ гарантирует размер, - это char. И размер равен 1. Размер всех остальных типов зависит от платформы.
Разве не для этого нужен <cstdint>? Он определяет такие типы, как uint16_6 и так далее.
Да, но размер большинства типов, скажем, длинных, точно не определен.
также cstdint еще не является частью текущего стандарта C++. см. boost / stdint.hpp для получения переносимого в настоящее время решения.
Это не неопределенное поведение. В стандарте говорится, что соответствующая платформа определяет размеры, а не стандарт, определяющий их.
Также не то, что стандарт не определяет, сколько 1 байт. Это как минимум 8 бит, но все, что разрешено выше, поэтому байт C++ не обязательно равен байту реальной жизни / в любом случае, я голосую за вас, поскольку это не заслуживает отрицательного голосования.
@JaredPar: Это не совсем так. long - это гарантированно будет не менее 32 бит.
@JohnDibling Я просмотрел этот пост и связанные со ссылками, но не смог найти точного источника, поскольку это 32 бита. Один пользователь заявил об этом, но не было ссылки на спецификацию, которая сделала бы
@JaredPar: Это сложный пост с множеством обсуждений, поэтому я подвел итог здесь. Суть в следующем: «5. Чтобы представить -2147483647 и +2147483647 в двоичном формате, вам нужно 32 бита».
По схожим причинам int имеет не менее 16 бит, а long long - не менее 64 (добавлен к стандарту в C++ 11).
NULLmemcpy для копирования перекрывающихся буферов.int64_t i = 1; i <<= 72 не определен)int i; i++; cout << i;)volatile или sig_atomic_t при получении сигналаlong int#ifХм ... NaN (x / 0) и Infinity (0/0) были охвачены IEEE 754, если C++ был разработан позже, почему он записывает x / 0 как undefined?
Re: «Обратная косая черта, за которой следует символ, который не является частью указанных escape-кодов в символьной или строковой константе». Это UB в C89 (§3.1.3.4) и C++ 03 (который включает C89), но не в C99. C99 говорит, что «результат не является токеном и требуется диагностика» (§6.4.4.4). Предположительно C++ 0x (который включает C89) будет таким же.
В стандарте C99 есть список неопределенного поведения в приложении J.2. Чтобы адаптировать этот список к C++, потребуется некоторая работа. Вам нужно будет изменить ссылки на правильные предложения C++, а не на предложения C99, удалить все, что не имеет отношения к делу, а также проверить, действительно ли все эти вещи не определены в C++, а также в C. Но это дает начало.
Довольно полезный и внушительный список. Мне нужно добавить это, так как он меня просто обжег: размер перечисления не определен; он должен быть достаточно большим, чтобы содержать int. «Каждый перечислимый тип должен быть совместим с char, целочисленным типом со знаком или целочисленным типом без знака. Выбор типа определяется реализацией, но должен быть способен представлять значения всех членов перечисления».
@ new123456 - не все модули с плавающей запятой совместимы с IEE754. Если C++ требует соответствия IEE754, компиляторам необходимо будет протестировать и обработать случай, когда RHS равен нулю, с помощью явной проверки. Сделав поведение неопределенным, компилятор может избежать этих накладных расходов, сказав: «Если вы используете FPU, отличный от IEE754, вы не получите поведения IEEE754 FPU».
«Сдвиг значений на величину, превышающую размер 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 (из-за модульной арифметики)
«Вычисление выражения, результат которого не входит в диапазон соответствующих типов» .... целочисленное переполнение хорошо определено для целочисленных типов UNSIGNED, но только не со знаком.
Я как раз собирался сделать тот же комментарий, что и @nacitarsevaht.
Порядок оценки параметров функции - неопределенные поведение. (Это не приведет к сбою вашей программы, взрыву или заказу пиццы ... в отличие от неопределенный поведение.)
Единственное требование - все параметры должны быть полностью вычислены перед вызовом функции.
Этот:
// 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: Я бы поспорил с вами об изменении значения здесь, но я знаю, что комитет по стандартам очень требователен к точному определению этих двух слов. Так что я просто изменю :-)
Мне повезло с этим. Меня это укусило, когда я учился в колледже, и у меня был профессор, который взглянул на это и рассказал мне о моей проблеме примерно за 5 секунд. Неизвестно, сколько времени я бы потратил на отладку в противном случае.
Переменные можно обновлять только один раз в выражении (технически один раз между точками последовательности).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Infact по меньшей мере один раз между двумя точками последовательности.
@Prasoon: Я думаю, вы имели в виду: в большинстве один раз между двумя точками последовательности. :-)
Компилятор может изменить порядок оцениваемых частей выражения (при условии, что значение не изменилось).
Из исходного вопроса:
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.
что подразумевается под точкой последовательности?
Ох ... это мерзко, тем более, что я видел ту точную структуру, рекомендованную в Java
Обратите внимание, что некоторые компиляторы действительно определяют поведение в этой ситуации. В VC++ 2005+, например, если a является изменчивым, необходимые барьеры памяти устанавливаются для предотвращения переупорядочения инструкций, чтобы сработала блокировка с двойной проверкой.
Мартин Йорк: <i> // (c) гарантированно произойдет после (a) и (b) </i> Это так? По общему признанию, в этом конкретном примере единственный сценарий, в котором это могло иметь значение, был бы, если бы 'i' был изменчивой переменной, отображаемой в аппаратный регистр, а [i] (старое значение 'i') было бы присвоено ей псевдонимом, но есть ли какие-либо гарантировать, что приращение произойдет до точки последовательности?
@supercat: Да, это гарантировано. Обе стороны = должны быть оценены, прежде чем его можно будет оценить.
@Martin York: выражение «i = i ++;» обычно приводится как пример неопределенного поведения в C (я знаю, что поведение определено в некоторых других языках, таких как Java). Если побочные эффекты приращения не гарантированно исчезнут и будут устранены к тому времени, когда появится «i = i ++;» происходит присвоение, почему они должны быть гарантированно завершены к моменту "a [i] = i ++;" имеет место?
@supercat: Хорошо, я понимаю, откуда вы. Вы правы, эффект от оператора ++ (на i) не гарантируется к этому моменту.
a[i] = i++; в любом случае является неопределенным поведением. Компилятор может делать больше, чем просто переупорядочивать его, он может игнорировать это или делать что-то с носом. i модифицируется на RHS, а на LHS он читается иначе, чем с целью определения записываемого значения. Игра окончена (если i не является типом класса с перегрузкой operator++, тогда любые модификации объекта внутри этой перегрузки надежно обертываются в точках последовательности, и это просто неуказанный порядок, а не UB).
@ Стив: Совершенно верно. Я просто пытался объяснить, почему он должен быть неопределенным (и некоторые возможные объяснения реализации).
@Martin: не уверен, что согласен с тем, что это объясняет, почему он должен быть неопределенным. Бывают и другие ситуации, в которых порядок операций не указан без вызова UB. Например, в случае, о котором я упоминал, где i - это тип класса, имитирующий целое число с подходящими operator++(int) и operator int(), ваши два варианта «или / или» все еще возможны, но нет UB. Тем не менее, возможно, говоря «неопределенное поведение», спрашивающий имеет в виду любую ситуацию, в которой поведение не полностью закреплено, в отличие от стандартного определения UB.
Ваш пример блокировки с двойной проверкой - беспощадный. ಥ_ಥ
Привет, я бродил, какие еще проблемы для второй версии реализации синглтона. Вроде идеально.
@zoujyjs: Если вы хотите создать синглтон с указателями, вы делаете это неправильно. См. Шаблон проектирования C++ Singleton
Да, я читал об этом также в пункте 04 «Эффективный C++». И это гарантированно поточно-ориентированное после C++ 0x. Но почему не указатели? А если нет, что если я просто верну разыменование указателей? Поскольку синглтон должен присутствовать на протяжении всей жизни процесса, мне не нужно было уничтожать его с помощью явного указателя.
В частности, я хочу знать, считается ли приведенный выше код достаточно хорошей реализацией DCLP.
@zoujyjs: Нет, это не хорошо в C++ 03. В C++ есть проблемы с потоковой передачей и блокировкой с двойной проверкой, что делает невозможным правильное выполнение; на нем есть знаменитая статья Саттера. Я считаю, что это исправлено для C++ 11, но вам нужно будет использовать новые функции, не описанные здесь.
@zoujyjs: Вот оно: C++ и опасности двойной проверки блокировки На самом деле Мейерс и Александреску
Присвоение константе после удаления constness с помощью const_cast<>:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
Мне больше всего нравится «Бесконечная рекурсия при создании экземпляров шаблонов», потому что я считаю, что это единственный вариант, в котором неопределенное поведение возникает во время компиляции.
Сделал это раньше, но я не понимаю, как это не определено. Совершенно очевидно, что вы делаете бесконечную рекурсию запоздало.
Проблема в том, что компилятор не может проверить ваш код и точно решить, будет ли он страдать от бесконечной рекурсии или нет. Это пример проблемы с остановкой. См .: stackoverflow.com/questions/235984/…
Да, это определенно проблема с остановкой
это привело к сбою моей системы из-за подкачки, вызванной слишком маленьким объемом памяти.
Константы препроцессора, которые не помещаются в int, также являются временем компиляции.
Помимо неопределенное поведение, есть также не менее неприятный поведение, определяемое реализацией.
Неопределенное поведение возникает, когда программа делает что-то, результат чего не определен стандартом.
Поведение, определяемое реализацией - это действие программы, результат которого не определен стандартом, но который реализация должна задокументировать. Примером могут служить «Многобайтовые символьные литералы» из вопроса о переполнении стека Есть ли компилятор C, который не может это скомпилировать?.
Поведение, определяемое реализацией, укусит вас только тогда, когда вы начнете переносить (но обновление до новой версии компилятора также переносится!)
Объекты уровня пространства имен в разных единицах компиляции никогда не должны зависеть друг от друга при инициализации, поскольку их порядок инициализации не определен.
Базовое понимание различных экологических ограничений. Полный список находится в разделе 5.2.4.1 спецификации C. Вот несколько;
На самом деле я был немного удивлен ограничением в 1023 меток case для оператора switch, я могу предвидеть, что оно будет превышено для сгенерированного кода / lex / парсеров довольно легко.
Если эти ограничения превышены, у вас неопределенное поведение (сбои, недостатки безопасности и т. д.).
Верно, я знаю, что это из спецификации C, но C++ разделяет эту базовую поддержку.
Если вы достигнете этих пределов, у вас будет больше проблем, чем неопределенное поведение.
Вы можете ЛЕГКО превысить 65535 байт в объекте, таком как STD :: vector
Использование memcpy для копирования между перекрывающимися областями памяти. Например:
char a[256] = {};
memcpy(a, a, sizeof(a));
Поведение не определено в соответствии со стандартом C, который входит в стандарт C++ 03.
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.
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.
Уверены ли вы. Это выглядит хорошо определенным.