Тернарный оператор в C
Почему эта программа дает неожиданные числа (например: 2040866504
, -786655336
)?
#include <stdio.h>
int main()
{
int test = 0;
float fvalue = 3.111f;
printf("%d", test? fvalue : 0);
return 0;
}
Почему это печатает неожиданные числа вместо 0
? если он должен делать неявный стиль? Эта программа для обучения не имеет ничего серьезного.
Ответы
Ответ 1
Скорее всего, ваша платформа передает значения с плавающей запятой в регистр с плавающей запятой и целочисленные значения в другом регистре (или в стеке). Вы сказали printf
искать целое число, поэтому поиск в целых числах регистров передается (или в стеке). Но вы передали ему float
, поэтому нуль был помещен в регистр с плавающей запятой, который printf
никогда не смотрел.
Тернарный оператор следует языковым правилам, чтобы определить тип его результата. Иногда это не может быть целым числом, а иногда и плавающим. Они могут быть разных размеров, хранятся в разных местах и т.д., Что сделало бы невозможным создание разумного кода для обработки обоих возможных типов результатов.
Это предположение. Возможно, происходит нечто совершенно другое. Поведение Undefined по умолчанию - Undefined. Такие вещи невозможно предсказать и очень трудно понять без большого опыта и знаний о деталях платформы и компилятора. Никогда не позволяйте кому-то убеждать вас в том, что UB в порядке или в безопасности, потому что он работает в своей системе.
Ответ 2
Потому что вы используете %d
для печати значения float
. Используйте %f
. Использование %d
для печати значения float
вызывает undefined поведение.
EDIT:
Что касается комментариев OP;
Почему он печатает случайные числа вместо 0?
Когда вы компилируете этот код, компилятор должен дать вам предупреждение:
[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat]
Это предупреждение самоочевидно, что эта строка кода вызывает поведение undefined. Это связано с тем, что спецификация преобразования %d
указывает, что printf
представляет собой преобразование значения int
из двоичного кода в строку десятичных цифр, тогда как %f
делает то же самое для значения float
. При передаче компилятору fvalue
известно, что он имеет тип float
, но, с другой стороны, он видит, что printf
ожидает аргумент типа int
. В таких случаях иногда он делает то, что вы ожидаете, иногда он делает то, что я ожидаю. Иногда он делает то, чего никто не ожидает (Хороший комментарий от Дэвид Шварц).
См. Тестовые примеры 1 и 2. Он отлично работает с %f
.
если он должен делать неявный стиль?
Нет.
Ответ 3
Несмотря на то, что существующие вышеперечисленные ответы верны, я думаю, что они слишком технические и игнорируют логику, которую может предложить программист-новичок:
Посмотрите на выражение, вызывающее замешательство в некоторых головах:
printf("%d", test? fvalue : 0);
^ ^ ^ ^
| | | |
| | | - the value we expect, an integral constant, hooray!
| | - a float value, this won't be printed as the test doesn't evaluate to true
| - an integral value of 0, will evaluate to false
- We will print an integer!
То, что видит компилятор, немного отличается. Он согласен с значением test
, означающим false
. Он согласен на fvalue
beeing a float
и 0
целое число. Однако он узнал, что разные возможные результаты тернарного оператора должны быть одного типа! int
и float
- нет. В этом случае "float
выигрывает", 0
становится 0.0f
!
Теперь printf
не безопасен для типов. Это означает, что вы можете ошибочно сказать "напечатайте целое число" и передайте float без уведомления компилятора. Именно это и произошло. Независимо от значения test
, компилятор вывел, что результат будет иметь тип float
. Следовательно, ваш код эквивалентен:
float x = 0.0f;
printf("%d", x);
На этом этапе вы испытываете поведение undefined. float
просто не является чем-то integral
ожидаемым %d
.
Наблюдаемое поведение зависит от используемого вами компилятора и машины. Вы можете увидеть танцевальных слонов, хотя большинство терминалов не поддерживают этот афаик.
Ответ 4
Когда у нас есть выражение E1 ? E2 : E3
, задействованы четыре типа. Выражения E1
, E2
и E3
имеют тип (и типы E2
и E3
могут быть разными). Кроме того, все выражение E1 ? E2 : E3
имеет тип.
Если E2
и E3
имеют один и тот же тип, это легко: общее выражение имеет этот тип. Мы можем выразить это в мета-нотации следующим образом:
(T1 ? T2 : T2) -> T2
"The type of a ternary expression whose alterantives are both of the same type T2
is just T2."
Если они не имеют одного и того же типа, все становится несколько интересным, и ситуация очень похожа на E2
и E3
, участвующих в арифметической операции. Например, если вы добавляете вместе int
и float
, операнд int
преобразуется в float
. Это то, что происходит в вашей программе. Типичная ситуация:
(int ? float : int) -> float
тест терпит неудачу, и поэтому значение int
0
преобразуется в значение float
0.0
.
Это значение float
несовместимо с спецификатором преобразования %d
printf
, для которого требуется int
.
Точнее, значение float
претерпевает еще одно. Когда a float
передается как один из возвращающих аргументов вариационной функции, он преобразуется в double
.
Таким образом, значение double
0.0 передается в printf
, где он ожидает int
.
В любом случае это поведение undefined: это непереносимый код, для которого стандартное определение языка C не имеет смысла.
С этого момента мы можем применить аргументы, основанные на платформе, почему мы не просто видим 0
. Предположим, что int
- это 32-разрядный, четырехбайтовый тип, а double
- это общий 64-разрядный, 8-байтовый, IEE754-представление и что для 0.0
используется бит-бит. Итак, почему не 32-разрядная часть этого all-bits-zero, обработанная printf
как значение int
0
?
Вполне возможно, что значение аргумента 64 бит double
заставляет 8-байтовое выравнивание, когда оно помещается в стек, возможно, перемещая указатель стека на четыре байта. И затем printf
вытаскивает мусор из этих четырех байтов, а не нулевые биты из значения double
.