Як працює ця програма?


88
#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", a);
    return 0;
}

Він відображає 0!! Як це можливо? Що таке міркування?


Я навмисно дав %dу printfзаяві знак вивчення поведінки printf.

Відповіді:


239

Це тому, що %dочікує, intале ви надали поплавок.

Використовуйте %e/ %f/ %gдля друку поплавця.


Про те, чому 0 друкується: Число з плаваючою комою перетвориться doubleперед відправкою printf. Число 1234,5 у подвійному поданні в маленькому ендіані становить

00 00 00 00  00 4A 93 40

A %dспоживає 32-бітове ціле число, тому нуль виводиться. (В якості тесту ви могли printf("%d, %d\n", 1234.5f);б отримати на виході 0, 1083394560.)


Що стосується того, чому floatконвертується double, як прототип printf int printf(const char*, ...), з 6.5.2.2/7,

Запис еліпсиса в деклараторі прототипу функції призводить до зупинки перетворення типу аргументу після останнього оголошеного параметра. Просування аргументів за замовчуванням виконується з кінцевими аргументами.

та з 6.5.2.2/6,

Якщо вираз, що позначає викликану функцію, має тип, який не включає прототип, цілочисельні підвищення здійснюються для кожного аргументу, а аргументи, що мають тип float, підвищуються до double. Вони називаються рекламними акціями за замовчуванням .

(Дякую Алок за те, що це з’ясував.)


4
+1 Найкраща відповідь. Він відповідає як "стандартно технічно правильно" чому, так і "ваше ймовірне впровадження" чому.
Chris Lutz,

12
Я вірю, що ви єдиний з 12 людей, хто насправді надав відповідь, яку він шукав.
Гейб,

11
Оскільки printfє варіадичною функцією, і стандарт говорить, що для варіадичних функцій а floatперетворюється на doubleперед передачею.
Alok Singhal

8
Зі стандарту C: "Запис еліпсиса в деклараторі прототипу функції змушує зупинити перетворення типу аргументу після останнього оголошеного параметра. Просування аргументів за замовчуванням виконується з кінцевими аргументами." і "... і аргументи, що мають тип float, підвищуються до подвійних. Вони називаються підвищеннями аргументів за замовчуванням ."
Alok Singhal

2
Використання неправильного специфікатора формату у printf()викликах невизначеної поведінки .
Prasoon Saurav,

45

Технічно не кажучи ні , кожна бібліотека реалізує свою власну, і , отже, ваш метод намагається дослідження «s поведінку, роблячи те , що ви робите, не буде багато користі. Можливо, ви намагаєтеся вивчити поведінку вашої системи, і якщо так, то прочитайте документацію та подивіться на вихідний код printfprintfprintfprintf чи він доступний для вашої бібліотеки.

Наприклад, на моєму Macbook я отримую вихідні дані 1606416304 разом із вашою програмою.

Сказавши, що, коли ви передаєте floata варіадичній функції, то floatпередається як a double. Отже, ваша програма еквівалентна оголошенню aякdouble .

Щоб вивчити байти a double, ви можете побачити цю відповідь на недавнє запитання тут, на SO.

Давайте зробимо це:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    unsigned char *p = (unsigned char *)&a;
    size_t i;

    printf("size of double: %zu, int: %zu\n", sizeof(double), sizeof(int));
    for (i=0; i < sizeof a; ++i)
        printf("%02x ", p[i]);
    putchar('\n');
    return 0;
}

Коли я запускаю вищезазначену програму, я отримую:

size of double: 8, int: 4
00 00 00 00 00 4a 93 40 

Отже, перші чотири байти doubleвиявилися рівними 0, і саме тому ви отримали 0результат printfвиклику.

Для отримання більш цікавих результатів ми можемо трохи змінити програму:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    int b = 42;

    printf("%d %d\n", a, b);
    return 0;
}

Коли я запускаю вищезазначену програму на своєму Macbook, я отримую:

42 1606416384

З тією ж програмою на машині Linux я отримую:

0 1083394560

Чому ви запрограмували друк назад? Мені чогось не вистачає? Якщо b - це int = 42, а '% d' - цілочисельний формат, чому це не друге значення, оскільки це друга змінна в аргументах printf? Це друкарська помилка?
Katastic Voyage

Можливо тому, що intаргументи передаються в різних регістрах, аніж doubleаргументи. printfwith %dприймає intаргумент 42, а другий, %dнапевно, друкує сміття, оскільки другого intаргументу не було.
Alok Singhal

20

Специфікатор %dповідомляє printfочікувати ціле число. Отже, перші чотири (або два, залежно від платформи) байти float інтерпретуються як ціле число. Якщо випадково вони дорівнюють нулю, друкується нуль

Бінарне представлення 1234,5 - це щось на зразок

1.00110100101 * 2^10 (exponent is decimal ...)

З компілятором C, який floatнасправді представляє подвійні значення IEEE754, байти були б (якби я не помилився)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

У системі Intel (x86) з невеликою віддачею (тобто найменш значущим байтом, який приходить першим), ця послідовність байтів змінюється таким чином, що перші чотири байти дорівнюють нулю. Тобто, що printfроздруковує ...

Див. Цю статтю у Вікіпедії щодо подання з плаваючою комою згідно з IEEE754


7

Це через представлення плаваючого в двійковому вигляді. Перетворення в ціле число залишає 0.


1
Здається, ти єдиний, хто зрозумів, про що він просив. Якщо я, звичайно, не помилився.
Мізіпзор

Я погоджуюсь, що відповідь є неточною та неповною, але не помилковою.
Шайхі,

7

Оскільки ви викликали невизначену поведінку: ви порушили контракт методу printf (), брешучи йому про його типи параметрів, тому компілятор може робити все, що заманеться. Це може зробити вихід програми "dksjalk - це нікчем !!!" і технічно це все одно буде правильно.


5

Причина в тому, що printf()це досить німа функція. Він взагалі не перевіряє типи. Якщо ви говорите, що перший аргумент - це int(і це те, з чим ви говорите %d), він вірить вам і займає лише байти, необхідні для int. У цьому випадку, якщо припустити, що ваша машина використовує чотирибайтові intта восьмибайтові double( floatперетворюється у doubleвнутрішню printf()частину), перші чотири байти aбудуть просто нулями, і це друкується.


3

Він не перетворить автоматично плаваюче в ціле число. Оскільки обидва мають різний формат зберігання. Отже, якщо ви хочете конвертувати, використовуйте (int) typecasting.

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}

2

Оскільки ви також позначили його тегом на C ++, цей код виконує перетворення, як ви, мабуть, очікуєте:

#include <iostream.h>

int main() {
    float a = 1234.5f;
    std::cout << a << " " << (int)a << "\n";
    return 0;
}

Вихід:

1234.5 1234

1

%d є десятковою

%f є плаваючим

дивіться більше з них тут .

Ви отримуєте 0, оскільки плаваючі та цілі числа представлені по-різному.


1

Вам просто потрібно використати відповідний специфікатор формату (% d,% f,% s тощо) з відповідним типом даних (int, float, рядок тощо).


Питання не в тому, як це виправити, а в тому, чому це працює так, як працює.
ya23,


0

привіт, йому довелося щось надрукувати, тому надрукували 0. Пам'ятайте, в С 0 - все інше!


1
Як це так? У C немає такого поняття, як усе інше.
Влад

якщо x щось, то! x == 0 :)
chunkyguy

якщо (iGot == "все") надрукувати "все"; ще надрукуйте "нічого";
nik

0

Це не ціле число. Спробуйте використовувати %f.


Думаю, питання полягає в тому, чому він просто не перетворює поплавок у int і не відображає "1234"?
Björn Pollex

не сподівайтесь, що c не дозволить вам робити щось, що не має логічного сенсу. Так, багато мов дадуть 1234, на який ви можете очікувати, і, можливо, навіть деякі реалізації c, я не думаю, що така поведінка визначена. C дозволяє повісити себе так, як батько, який дозволяє вам спробувати зламатися.
повтор

Оскільки С розроблений для гнучкості.
тангри
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.