Вопрос по c, initialization, undefined-behavior – (Почему) использует неинициализированную переменную неопределенного поведения?

72

Если у меня есть:

unsigned int x;
x -= x;

ясно, чтоx should быть нулем после этого выражения, но везде я смотрю, они говорятbehavior этого кода не определено, а не просто значениеx (пока до вычитания).

Два вопроса:

Is the behavior of this code indeed undefined?
(E.g. Might the code crash [or worse] on a compliant system?)

If so, why does C say that the behavior is undefined, when it is perfectly clear that x should be zero here?

i.e. What is the advantage given by not defining the behavior here?

Понятно, что компилятор может просто использоватьwhatever ценность мусора, которую он счел "удобной" внутри переменной, и она будет работать как задумано ... что не так с этим подходом?

Какое преимущество дает определение особого случая для поведения здесь? Конечно, давайте все сделаем наши программы и библиотеки больше и медленнее, потому что @Mehrdad хочет избежать инициализации переменной в одном конкретном и редком случае. Paul Tomblin
Интересно, что утверждение x = 0; обычно преобразуется в xor x, x в сборке. Это почти то же самое, что вы пытаетесь сделать здесь, но с xor вместо вычитания. 0xFE
@ W 'rkncacnter Я не согласен с тем, что это дурак. Независимо от того, какое значение он принимает, ФП ожидает, что оно будет равно нулю послеx -= x, Вопрос в томwhy доступ к неинициализированным значениям вообще является UB. Mysticial
& APOS; то есть. Какое преимущество дает отсутствие определения поведения здесь? & APOS; - Я бы подумал, что преимущество стандарта в том, что он не перечисляет бесконечность выражений со значениями, которые не зависят от одной или нескольких переменных, чтобы быть очевидными. В то же время, @Paul, такое изменение в стандарте не сделало бы программы и библиотеки больше. Jim Balter

Ваш Ответ

7   ответов
21

Стандарт C дает компиляторам много возможностей для выполнения оптимизаций. Последствия этих оптимизаций могут быть удивительными, если вы предполагаете наивную модель программ, в которой для неинициализированной памяти задан некоторый случайный битовый шаблон и все операции выполняются в порядке их записи.

Note: the following examples are only valid because x never has its address taken, so it is “register-like”. They would also be valid if the type of x had trap representations; this is rarely the case for unsigned types (it requires “wasting” at least one bit of storage, and must be documented), and impossible for unsigned char. If x had a signed type, then the implementation could define the bit pattern that is not a number between -(2n-1-1) and 2n-1-1 as a trap representation. See Ответ Дженса Гастдта.

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

unsigned x, y, z;   /* 0 */
y = 0;              /* 1 */
z = 4;              /* 2 */
x = - x;            /* 3 */
y = y + z;          /* 4 */
x = y + 1;          /* 5 */

Когда строка 3 оценивается,x еще не инициализирован, поэтому (по причинам компилятора) строка 3 должна быть некоторой случайностью, которая не может произойти из-за других условий, которые компилятор не был достаточно умен, чтобы понять. посколькуz не используется после строки 4, иx не используется перед строкой 5, один и тот же регистр может использоваться для обеих переменных. Итак, эта небольшая программа компилируется для следующих операций с регистрами:

r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;

Окончательное значениеx это окончательное значениеr0и окончательное значениеy это окончательное значениеr1, Эти значения x = -3 и y = -4, а не 5 и 4, как если быx был правильно инициализирован.

Для более сложного примера рассмотрим следующий фрагмент кода:

unsigned i, x;
for (i = 0; i < 10; i++) {
    x = (condition() ? some_value() : -x);
}

Предположим, что компилятор обнаруживает, чтоcondition не имеет побочных эффектов. посколькуcondition не модифицируетxкомпилятор знает, что первый прогон цикла не может получить доступx так как он еще не инициализирован. Поэтому первое выполнение тела цикла эквивалентноx = some_value()Нет необходимости проверять условие. Компилятор может скомпилировать этот код так, как если бы вы написали

unsigned i, x;
i = 0; /* if some_value() uses i */
x = some_value();
for (i = 1; i < 10; i++) {
    x = (condition() ? some_value() : -x);
}

Способ, которым это может быть смоделировано внутри компилятора, состоит в том, чтобы учитывать, что любое значение, зависящее отx имеетwhatever value is convenient покаx неинициализирован. Поскольку поведение, когда неинициализированная переменная не определена, а не переменная, имеющая просто неопределенное значение, компилятору не нужно отслеживать какие-либо специальные математические отношения между любыми удобными значениями. Таким образом, компилятор может анализировать приведенный выше код следующим образом:

during the first loop iteration, x is uninitialized by the time -x is evaluated. -x has undefined behavior, so its value is whatever-is-convenient. The optimization rule condition ? value : value applies, so this code can be simplified to condition; value.

Когда сталкивается с кодом в вашем вопросе, этот же компилятор анализирует, когдаx = - x оценивается, значение-x это все, что удобно. Таким образом, назначение может быть оптимизировано.

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

Этот гипотетический компилятор отображает каждую переменную на другой странице памяти и устанавливает атрибуты страницы так, чтобы чтение из неинициализированной переменной вызывало ловушку процессора, которая вызывает отладчик. Любое присвоение переменной сначала гарантирует, что ее страница памяти отображается нормально. Этот компилятор не пытается выполнить какую-либо расширенную оптимизацию & # x2014; он находится в режиме отладки и предназначен для простого обнаружения ошибок, таких как неинициализированные переменные. когдаx = - x оценивается, правая часть вызывает ловушку, и отладчик запускается.

+1 Хорошее объяснение, стандарт заботится об этой ситуации. Для продолжения этой истории см. Мой ответ ниже. (слишком долго, чтобы иметь в качестве комментария).
@Gilles: по крайней мере Clang делает те оптимизации, которые вы упомянули:(1), (2), (3).
... совершенно непредсказуемым образом в этом случае. Реализация, которая в этом случае надежно создаст ловушку, определяемую реализацией, в некоторых случаях может рассматриваться как имеющая более высокое качество, но поведение совершенно непредсказуемо кажется мне формой поведения самого низкого качества для практически любой цели.
Какое практическое преимущество заключается в том, что такой процесс обрабатывает лягушка? Если нижестоящий код никогда не использует значениеxтогда все операции с ним могут быть опущены независимо от того, было ли определено его значение или нет. Если код следует, например,if (volatile1) x=volatile2; ... x = (x+volatile3) & 255; был бы одинаково счастлив с любым значением 0-255, котороеx может содержать в том случае, когдаvolatile1 дал бы ноль, я думаю, реализация, которая позволит программисту пропустить ненужную запись вx следует рассматривать как более высокое качество, чем то, которое будет вести себя ...
@JensGustedt О, ваш ответ содержит очень важный момент, который я (и другие) упустил: если тип не имеет значений ловушек, что для типа без знака требует & # x201C; wasting & # x201D; хотя бы один бит,x имеет неинициализированное значение, но поведение при доступе будет определено, если x не имеет поведения, подобного регистру.
6

Для любой переменной любого типа, которая не инициализирована или по другим причинам имеет неопределенное значение, для кода, читающего это значение, применяется следующее:

In case the variable has automatic storage duration and does not have its address taken, the code always invokes undefined behavior [1]. Otherwise, in case the system supports trap representations for the given variable type, the code always invokes undefined behavior [2].

Otherwise if there are no trap representations, the variable takes an unspecified value. There is no guarantee that this unspecified value is consistent each time the variable is read. However, it is guaranteed not to be a trap representation and it is therefore guaranteed not to invoke undefined behavior [3].

The value can then be safely used without causing a program crash, although such code is not portable to systems with trap representations.

[1]: C11 6.3.2.1:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

[2]: C11 6.2.6.1:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation.

[3] C11:

3.19.2
indeterminate value
either an unspecified value or a trap representation

3.19.3
unspecified value
valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance
NOTE An unspecified value cannot be a trap representation.

3.19.4
trap representation
an object representation that need not represent a value of the object type

DR 451 и 260
@Vality В реальном мире 99,9999% всех компьютеров представляют собой два дополнительных процессора без представления прерываний. Поэтому представление ловушек не является нормой, и обсуждение поведения на таких реальных компьютерах весьма актуально. Предполагать, что дико экзотические компьютеры являются нормой, бесполезно. Представления ловушек в реальном мире настолько редки, что наличие термина «представление ловушек» в стандарте в основном следует рассматривать как стандартный дефект, унаследованный с 1980-х годов. Как поддержка одного дополнения и подписи & amp; величина компьютеров.
Опять же, в ответе комитета на отчет о дефектах говорится, что: «Ответ на вопрос 2 заключается в том, что любая операция, выполняемая с неопределенными значениями, будет иметь неопределенное значение в результате». и & quot; ответ на вопрос 3 заключается в том, что библиотечные функции будут демонстрировать неопределенное поведение при использовании неопределенных значений. & quot;
Я бы сказал, что это решает вопрос "Это всегда неопределенное поведение" поскольку абстрактная машина C может иметь представление ловушек. Тот факт, что ваша реализация не использует их, не определяет код. На самом деле строгое чтение даже не настаивает на том, что представления ловушек должны быть аппаратными, из того, что я не могу сказать, я не понимаю, почему компилятор не может решить, что конкретный битовый шаблон является ловушкой, проверяйте его каждый раз, когда читается переменная и вызвать UB.
Обратите внимание, что возможноunsigned char освобождается от этого по причинам, указанным выше.
11

Да, это не определено. Код может привести к сбою. С говорит, что поведение не определено, потому что нет особой причины делать исключение из общего правила. Преимущество такое же, как и во всех других случаях неопределенного поведения - компилятору не нужно выводить специальный код для этой работы.

Clearly, the compiler could simply use whatever garbage value it deemed "handy" inside the variable, and it would work as intended... what's wrong with that approach?

Почему вы думаете, что этого не происходит? Это именно тот подход, который принят. Компилятор не требуется, чтобы заставить его работать, но он не обязан сбивать его.

@JensGustedt: я не понимаю ваш комментарий. Можете ли вы уточнить?
Однако компилятору также не требуется специальный код для этого. Просто распределяя пространство (как всегда) иnot intializing переменная дает ему правильное поведение. Я не думаю, что для этого нужна особая логика. Mehrdad
Потому что вы просто утверждаете, что есть общее правило, не ссылаясь на него. Как таковая, это всего лишь попытка «доказательства властью». это не то, что я ожидаю на SO. И из-за неэффективной аргументации, почему это не может быть неопределенным значением. Единственная причина, по которой это UB в общем случае, заключается в том, чтоx может быть объявлен какregisterто есть его адрес никогда не берется. Я не знаю, знали ли вы об этом (если вы эффективно это скрывали), но в правильном ответе должно быть упомянуто об этом.
-1 потому что вы вообще не обосновываете свою претензию. Есть ситуации, когда было бы правильно ожидать, что компилятор просто принимает значение, записанное в ячейке памяти.
1) Конечно, они могли бы иметь. Но я не могу думать ни о каком аргументе, который мог бы сделать это лучше. 2) Платформе известно, что на значение неинициализированной памяти нельзя положиться, поэтому она может свободно ее изменять. Например, он может обнулять неинициализированную память в фоновом режиме, чтобы обнулять страницы, готовые для использования при необходимости. (Рассмотрим, если это произойдет: 1) Мы читаем значение для вычитания, скажем, мы получаем 3. 2) Страница обнуляется, потому что она неинициализирована, изменяя значение на 0. 3) Мы делаем атомарный вычитание, выделяя страницу делая значение -3. К сожалению.)
16

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

(6.2.6.1 on a late C11 draft says) Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation.

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

Итак, могу ли я сказать, что поведение хорошо определено в случае целых чисел и x86?
Я ненавижу, когда кто-то оскорбляет всех остальных, потому что кто-то не понимает проблему. Является ли поведение неопределенным, полностью зависит от того, что говорится в стандарте. Да, и в сценарии уравнения нет ничего практического ... он полностью вымышлен.
Ну, теоретически вы могли бы иметь компилятор, который решил использовать только 28-битные целые числа (на x86) и добавить специальный код для обработки каждого сложения, умножения (и т. Д.) И обеспечить, чтобы эти 4 бита не использовались (или в противном случае генерировать SIGSEGV) ). Неинициализированное значение может вызвать это.
@VladLazarenko: речь идет о C, а не о конкретных процессорах. Любой может с легкостью спроектировать ЦП с битовыми шаблонами для целых чисел, которые сводят его с ума. Рассмотрим процессор, который имеет «сумасшедший бит» в своих регистрах.
@Vlad Lazarenko: процессоры Itanium имеют флаг NaT (не вещь) для каждого целочисленного регистра. Флаг NaT используется для управления спекулятивным исполнением и может задерживаться в регистрах, которые не были должным образом инициализированы перед использованием. Чтение из такого регистра с установленным битом NaT дает исключение. Увидетьblogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx
14

(Этот ответ относится к C 1999. Информацию о C 2011 см. В ответе Jens Gustedt.)

Стандарт C не говорит, что использование значения объекта автоматической продолжительности хранения, которое не инициализировано, является неопределенным поведением. Стандарт C 1999 в 6.7.8 10 гласит: «Если объект, имеющий автоматическую продолжительность хранения, не инициализируется явно, его значение является неопределенным. & # X201D; (Этот параграф далее определяет, как инициализируются статические объекты, поэтому единственные неинициализированные объекты, которые нас интересуют, это автоматические объекты.)

3.17.2 определяет & # x201C; неопределенное значение & # x201D; как & # x201C; либо неопределенное значение, либо представление прерывания & # x201D ;. 3.17.3 определяет & # x201C; неопределенное значение & # x201D; в качестве & # x201C; действительного значения соответствующего типа, если настоящий международный стандарт не предъявляет требований к выбору значения в любом случае & # x201D ;.

Итак, если неинициализированныйunsigned int x имеет неопределенное значение, тоx -= x должен произвести ноль. Это оставляет вопрос того, может ли это быть представление ловушки. Доступ к значению ловушки приводит к неопределенному поведению, согласно 6.2.6.1 5.

У некоторых типов объектов могут быть представления ловушек, такие как сигнальные NaN чисел с плавающей запятой. Но целые числа без знака особенные. В соответствии с 6.2.6.2 каждый из N битов значения беззнакового целого представляет степень 2, и каждая комбинация битов значения представляет одно из значений от 0 до 2N-1. Таким образом, целые числа без знака могут иметь представление ловушек только из-за некоторых значений в их битах заполнения (таких как бит четности).

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

Еслиx имеет представление ловушки, тоx -= x может быть в ловушке, верно? Тем не менее, +1 для обозначения целых чисел без знака без лишних битов должно иметь определенное поведение - это явно противоположно другим ответам и (согласно цитате) кажется, что это подразумевает стандарт. Mehrdad
Да, если типx имеет представление ловушки, тоx -= x может заманить в ловушку Даже простоx используется в качестве значения может заманить в ловушку. (Это безопасно использоватьx как lvalue; запись в объект не будет затронута представлением ловушки, которое находится в нем.)
−1 Оператор & quot; Если на вашей целевой платформе неподписанное int не имеет битов заполнения, то неинициализированное unsigned int не может иметь представление прерывания, а использование его значения не может привести к неопределенному поведению. & Quot; не учитывает такие схемы, как биты Na64 x64.
неподписанные типы редко имеют представление ловушки
квотированиеRaymond Chen, "На ia64 каждый 64-битный регистр фактически равен 65 битам. Дополнительный бит называется & # x201C; NaT & # x201D; что означает & # x201C; не вещь & # x201D ;. Бит устанавливается, когда регистр не содержит допустимого значения. Думайте об этом как о целочисленной версии с плавающей точкой NaN. ... если у вас есть регистр, значение которого равно NaT, и вы используете его неправильно (например, попытаетесь сохранить его значение в памяти), процессор вызовет исключение STATUS_REG_NAT_CONSUMPTION & quot ;. Т.е. бит ловушки может быть полностью вне значения.
76

Да, это поведение не определено, но по другим причинам, чем большинство людей знают.

Во-первых, использование унифицированного значения само по себе не является неопределенным поведением, но значение просто неопределенное. Доступ к этому - UB, если значение является представлением ловушки для типа. Типы без знака редко имеют представление ловушек, поэтому вы были бы относительно безопасны на этой стороне.

То, что делает поведение неопределенным, является дополнительным свойством вашей переменной, а именно то, что оно "могло быть объявлено с помощьюregister& Quot; это его адрес никогда не берется. Такие переменные обрабатываются специально, потому что существуют архитектуры с реальными регистрами ЦП, которые имеют своего рода дополнительное состояние, которое «неинициализировано». и это не соответствует значению в домене типа.

Edit: Соответствующая фраза стандарта - 6.3.2.1p2:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

И чтобы было понятнее, следующий кодis законно при любых обстоятельствах:

unsigned char a, b;
memcpy(&a, &b, 1);
a -= a;
Here the addresses of a and b are taken, so their value is just indeterminate. Since unsigned char never has trap representations that indeterminate value is just unspecified, any value of unsigned char could happen. At the end a must hold the value 0.

Edit2: a а такжеb имеют неопределенные значения:

3.19.3 unspecified value
valid value of the relevant type where this International Standard imposes no requirements on which value is chosen in any instance

@JensGustedt: это неmemcpy отвлечение внимания, т. е. ваш пример по-прежнему не применяется, если он был заменен на*&a = *&b;.
@R .. Я больше не уверен. В списке рассылки комитета C идет постоянное обсуждение, и кажется, что все это - большой беспорядок, а именно большой разрыв между тем, что является (или было) предполагаемым поведением, и тем, что фактически написано. Что ясно, так это то, что доступ к памяти какunsigned char и поэтомуmemcpy помогает, один для*& менее понятно. Я сообщу, как только это уляжется.
@conio Вы правы для всех типов, кромеunsigned char, но этот ответ используетunsigned char, Обратите внимание: строго соответствующая программа может рассчитатьsizeof(unsigned) * CHAR_BIT и определить, основываясь наUINT_MAX, что конкретные реализации не могут иметь представления ловушек дляunsigned, После того как эта программа определила это определение, она может приступить к выполнению того же действия, что и этот ответ.unsigned char.
Продолжение комментария: «Некоторые комбинации битов заполнения могут генерировать представления прерываний, например, если один бит заполнения является битом четности. В любом случае, никакая арифметическая операция над допустимыми значениями не может генерировать представление прерываний, кроме как как часть исключительного условия, такого как переполнение, и это не может происходить с неподписанными типами. & Quot; - Это здоровоonce у нас есть действительное значение для работы, но неопределенное значениеmight быть представлением прерываний перед инициализацией (например, бит четности установлен неправильно).
Возможно, я что-то упустил, но мне кажется, чтоunsigneds может иметь представление ловушки. Можете ли вы указать на ту часть стандарта, которая так говорит? Я вижу в & # xA7; 6.2.6.2/1 следующее: & quot; Для целых типов без знака, кромеunsigned charбиты представления объекта должны быть разделены на две группы: биты значения и биты заполнения (не должно быть ни одной из последних). ... это должно быть известно как представление значения. Значения любых битов заполнения не определены. & # X2074; & # x2074; & # x207E; & Quot; с комментарием: & quot; & # x2074; & # x2074; & # x207E; Некоторые комбинации битов заполнения могут генерировать представления прерываний & quot ;.
1

Хотя многие ответы сосредоточены на процессорах, которые улавливают доступ к неинициализированным регистрам, причудливое поведение может возникнуть даже на платформах, у которых таких ловушек нет, при использовании компиляторов, которые не прилагают особых усилий для использования UB. Рассмотрим код:

volatile uint32_t a,b;
uin16_t moo(uint32_t x, uint16_t y, uint32_t z)
{
  uint16_t temp;
  if (a)
    temp = y;
  else if (b)
    temp = z;
  return temp;  
}

компилятор для платформы, такой как ARM, где все инструкции, кроме нагрузки и хранилища, работающие с 32-битными регистрами, могут разумно обрабатывать код в моде, эквивалентной:

volatile uint32_t a,b;
// Note: y is known to be 0..65535
// x, y, and z are received in 32-bit registers r0, r1, r2
uin32_t moo(uint32_t x, uint32_t y, uint32_t z)
{
  // Since x is never used past this point, and since the return value
  // will need to be in r0, a compiler could map temp to r0
  uint32_t temp;
  if (a)
    temp = y;
  else if (b)
    temp = z &, 0xFFFF;
  return temp;  
}

Если любое из изменчивых чтений даст ненулевое значение, r0 будет загружено со значением в диапазоне 0 ... 65535. В противном случае он выдаст все, что удерживал при вызове функции (то есть значение, переданное в x), что может не быть значением в диапазоне 0..65535. В стандарте отсутствует какая-либо терминология для описания поведения значения, тип которого равен uint16_t, но значение которого выходит за пределы диапазона 0,65535, за исключением того, что любое действие, которое может вызвать такое поведение, вызывает UB.

Интересно. Так вы говорите, что принятый ответ неверен? Или вы говорите, что это правильно в теории, но на практике компиляторы могут делать более странные вещи? Mehrdad
Мне кажется, что вы описываете именно то, что содержится в принятом ответе - что если переменнаяcould были объявлены сregisterтогда он может иметь дополнительные биты, которые делают поведение потенциально неопределенным. Это именно то, что вы говорите, верно? Mehrdad
@Mehrdad: для реализаций характерно поведение, выходящее за пределы того, что было бы возможно в отсутствие UB. Я думаю, что было бы полезно, если бы стандарт признал концепцию частично неопределенного значения, "распределенное" биты будут вести себя таким образом, который в худшем случае не определен, но с дополнительными старшими битами, которые ведут себя недетерминированно (например, если результат вышеупомянутой функции сохраняется в переменной типаuint16_t, эта переменная может иногда читаться как 123, а иногда 6553623). Если результат заканчивается игнорированием ...
@Mehrdad: принятый ответ сфокусирован на архитектурах, регистры которых имеют дополнительный «неинициализированный» код. состояние и прерывание, если загружен неинициализированный регистр. Такие архитектуры существуют, но не являются обычным явлением. Я описываю сценарий, гдеcommonplace аппаратные средства могут демонстрировать поведение, которое выходит за рамки всего, что предусмотрено Стандартом C, но будет полезно ограничено, если компилятор не добавит свой собственный дополнительный дурацкий характер в смесь. Например, если у функции есть параметр, который выбирает операцию для выполнения, и некоторые операции возвращают полезные данные, а другие - нет, ...
... или использовать таким образом, чтобы любые возможные способы его прочтения приводили бы к конечным результатам, отвечающим требованиям, и существование частично неопределенного значения не должно быть проблемой. С другой стороны, в стандарте нет ничего, что позволяло бы существование частично неопределенных значений в любых обстоятельствах, когда стандарт налагал бы какие-либо поведенческие требования вообще.

Похожие вопросы