Подтвердить что ты не робот

Тернарный оператор в C

Почему эта программа дает неожиданные числа (например: 2040866504, -786655336)?

#include <stdio.h> 
int main()
{
     int test = 0;
     float fvalue = 3.111f;
     printf("%d", test? fvalue : 0);

     return 0;
}

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

4b9b3361

Ответ 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.