2013-09-28 2 views
13

Почему эта программа дает неожиданные цифры (например: 2040866504, -786655336)?Тернарный оператор в C

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

    return 0; 
} 

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

+0

Что именно вы подразумеваете под «случайным»? Вы пытаетесь напечатать число с плавающей точкой как целое число, но это число должно быть '0', которое имеет одинаковое представление бит в обоих типах. –

+1

Случайный, или просто некорректный? –

+0

Этот вопрос кажется не по теме, потому что речь идет о том, кто знает что. –

ответ

18

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

Тернарный оператор следует языковым правилам, чтобы определить тип его результата. Иногда это не может быть целым числом, а иногда и плавающим. Они могут быть разных размеров, хранятся в разных местах и ​​т. Д., Что сделало бы невозможным создание разумного кода для обработки обоих возможных типов результатов.

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

+0

Единственное, чего не хватает в этом ответе, - это то, почему параметр был с плавающей запятой в первую очередь. –

+0

Я добавил дополнительную информацию об этом в средний абзац. –

+0

wow ... большое описание !! Теперь концепция понятна :-) – Tonmoy

11

Поскольку вы используете %d для печати значения float. Используйте %f. Использование %d для печати значения float вызывает неопределенное поведение.


EDIT: Что касается замечаний OP в;

Почему это печатает случайные числа вместо 0?

При компиляции этого кода, компилятор должен дать вам предупреждение:

[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat] 

Это предупреждение само за себя, что эта строка кода вызова неопределенное поведение. Это связано с тем, что в спецификации преобразования %d указано, что printf предназначено для преобразования значения int из двоичного кода в строку десятичных цифр, тогда как %f делает то же самое для значения float. При передаче fvalue компилятор знает, что он имеет тип float, но, с другой стороны, он видит, что printf ожидает аргумент типа int. В таких случаях иногда делает то, что вы ожидаете, иногда он делает то, что я ожидаю. Иногда он делает то, чего никто не ожидает (Хороший комментарий от David Schwartz).
См. Тестовые примеры 1 и 2. Он отлично работает с %f.

Должен ли он предполагать делать неявный тип?

Номер

+0

Правда, но я не думаю, что это имеет значение с конкретным кодом, показанным здесь. –

+0

Почему это печатает случайные числа вместо 0? если он должен делать неявный стиль? – Tonmoy

+4

@MarkRansom; Зачем? См. Тестовые примеры [1] (http://ideone.com/Ig5uub) и [2] (http://ideone.com/woe4XU). – haccks

8

Хотя существующие upvoted правильные ответы, я думаю, что они слишком технические и игнорировать логику начинающий программист может иметь:

Давайте посмотрим на заявление вызывает путаницу в некоторых глав:

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, являющимся целым числом, float и 0. Однако он узнал, что разные возможные результаты тернарного оператора должны быть одного типа! int и float нет. В этом случае «float побед», 0 становится 0.0f!

В настоящее время printf не является безопасным. Это означает, что вы можете ошибочно сказать «напечатайте целое число» и передайте float без уведомления компилятора. Именно это и произошло. Независимо от того, что такое значение test, компилятор вывел, что результат будет иметь тип float. Следовательно, ваш код эквивалентен:

float x = 0.0f; 
printf("%d", x); 

На данный момент вы испытываете неопределенное поведение. float просто не является чем-то integral, что ожидается от %d.

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

1

Когда у нас есть выражение 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 

тест не пройден, и поэтому значение 0int преобразуется в значение 0.0float.

Это float значение не является совместимым с %d спецификатором преобразования из printf, который требует int.

Точнее, значение float претерпевает еще одно.Когда float передается как один из возвращающих аргументов вариационной функции, он преобразуется в double.

Таким образом, на самом деле значение double 0.0 передается в printf, где ожидается int.

В любом случае это неопределенное поведение: это непереносимый код, для которого стандартное определение языка C не имеет смысла.

С этого момента мы можем применить аргументы, основанные на платформе, почему мы не просто видим 0. Предположим, что int - это 32-разрядный, четырехбайтовый тип, а double - это общее представление 64-битного, 8-байтового, IEE754 и что для 0.0 используется бит с битами. Итак, почему же не 32-разрядная часть этого бита-нуля равна printf как int значение 0?

Вполне возможно, что значение аргумента 64 бит double заставляет 8-байтовое выравнивание, когда оно помещено в стек, возможно, перемещая указатель стека на четыре байта. И тогда printf вытаскивает мусор из этих четырех байтов, а не нулевые биты из значения double.

Смежные вопросы