Использование %f для печати целочисленной переменной

Вывод следующей программы c: 0.000000

Есть ли логика за выводом или зависит от компилятора ответа, или я просто получаю значение мусора?

#include<stdio.h>

int main()
{
    int x=10;
    printf("%f", x);
    return 0;
}

PS: - Я знаю, что пытаться напечатать целочисленное значение с помощью %f глупо. Я просто спрашиваю это с теоретической точки зрения.


person Prateek    schedule 26.07.2012    source источник
comment
Я вижу странные вещи: int x=10, y=4,z=4; printf(x %f y %d z %d\n, x, y, z);   -  person perreal    schedule 26.07.2012
comment
Это 32 или 64 битная платформа?   -  person Maxim Egorushkin    schedule 26.07.2012
comment
@JoelCornett, вам понадобится (float)x. %lf это double.   -  person chris    schedule 26.07.2012
comment
@perreal да, вывод в ваш код тоже довольно странный. Теперь, почему z печатает значение мусора?   -  person Prateek    schedule 26.07.2012
comment
Возможно, этот похожий ответ может оказаться полезным.   -  person Kerrek SB    schedule 26.07.2012
comment
При компиляции выдает предупреждение, так что, скорее всего, это мусор.   -  person Eitan T    schedule 26.07.2012
comment
@Prateek, возможно, он читает 8 байт?   -  person perreal    schedule 26.07.2012
comment
@MaximYegorushkin Запустил программу на ideone   -  person Prateek    schedule 26.07.2012
comment
@perreal да, возможно, float читает 4 дополнительных байта (т.е. x и y). а для z выводится значение мусора   -  person Prateek    schedule 26.07.2012
comment
@Prateek, хотя вы можете видеть последовательное поведение, вы никогда не можете рассчитывать ни на что, когда в игру вступает неопределенное поведение.   -  person chris    schedule 26.07.2012
comment
@chris Хм .... %f равно double, потому что значения float повышаются до double при передаче через многоточие   -  person qehgt    schedule 26.07.2012
comment
@qehgt, да, я заметил из ответа Керрека. Я не знал, что поплавки тоже так делают. Это заставляет меня задаться вопросом, почему %f и %lf разделены.   -  person chris    schedule 26.07.2012


Ответы (6)


Из последнего проекта C11:

§7.16.1.1/2

...if type is not compatible with the type of the actual next argument 
(as promoted according to the default argument promotions), the behavior 
is undefined, except for the following cases:

— one type is a signed integer type, the other type is the corresponding 
unsigned integer type, and the value is representable in both types;
— one type is pointer to void and the other is a pointer to a character type.
person chris    schedule 26.07.2012
comment
Проект N1570 более актуален. - person Keith Thompson; 26.07.2012
comment
@EitanT, я не знаю, как это будет на С++. Главная-›C-›Язык программирования C-›N1548 заставляет меня думать, что это не C++. Возможно, вы путаетесь с С++ 11? - person chris; 26.07.2012
comment
@KeithThompson, там то же самое, поэтому я добавил это в закладки и обновил ответ, чтобы включить его вместо этого. - person chris; 26.07.2012
comment
@Prateek, неопределенное поведение означает, что он может делать что угодно. Мусорное значение, закрывающее вашу программу, вызывающее вторжение инопланетян. Тебе известно. - person chris; 26.07.2012
comment
@chris Извините, я почему-то думал, что вы говорите C ++ 11. Мне нужен отпуск... - person Eitan T; 26.07.2012
comment
@KeithThompson, не могли бы вы подсказать, где найти это на сайте? Я думал, что путь, который я указал выше, будет обновляться по мере их выхода, но, видимо, нет. - person chris; 26.07.2012
comment
@chris: я не знаю, как найти N1570, отличный от этого URL. Обычно я получаю информацию от comp.std.c и comp.lang.c в Usenet. N1569 и N1570 имеют одинаковое содержимое; У N1570 есть отличия (кажется, от N1256). - person Keith Thompson; 26.07.2012

Самое главное помнить, что, как указывает Крис, поведение не определено. Если бы это было в реальной программе, единственное, что можно было бы сделать, это исправить код.

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

Формат printf "%f" ожидает аргумент типа double и печатает его в десятичной форме без экспоненты. Очень маленькие значения будут напечатаны как 0.000000.

Когда вы делаете это:

int x=10;
printf("%f", x);

мы можем объяснить видимое поведение, учитывая несколько предположений о платформе, на которой вы находитесь:

  • int составляет 4 байта
  • double составляет 8 байт
  • Аргументы int и double передаются в printf с использованием одного и того же механизма, вероятно, в стеке.

Таким образом, вызов (правдоподобно) поместит int значение 10 в стек как 4-байтовое количество, а printf возьмет 8 байтов данных из стека и обработает их как представление double. 4 байта будут представлением 10 (в шестнадцатеричном формате, 0x0000000a); остальные 4 байта будут мусором, скорее всего нулевым. Мусором могут быть как старшие, так и младшие 4 байта из 8-байтового количества. (Или что-нибудь еще; помните, что поведение не определено.)

Вот демонстрационная программа, которую я только что собрал. Вместо того, чтобы злоупотреблять printf, он копирует представление объекта int в объект double, используя memcpy().

#include <stdio.h>
#include <string.h>

void print_hex(char *name, void *addr, size_t size) {
    unsigned char *buf = addr;
    printf("%s = ", name);
    for (int i = 0; i < size; i ++) {
        printf("%02x", buf[i]);
    }
    putchar('\n');
}

int main(void) {
    int i = 10;
    double x = 0.0;
    print_hex("i (set to 10)", &i, sizeof i);
    print_hex("x (set to 0.0)", &x, sizeof x);

    memcpy(&x, &i, sizeof (int));
    print_hex("x (copied from i)", &x, sizeof x);
    printf("x (%%f format) = %f\n", x);
    printf("x (%%g format) = %g\n", x);

    return 0;
}

Вывод в моей системе x86:

i (set to 10) = 0a000000
x (set to 0.0) = 0000000000000000
x (copied from i) = 0a00000000000000
x (%f format) = 0.000000
x (%g format) = 4.94066e-323

Как видите, значение double очень мало (подробности можно найти в справочнике по формату с плавающей запятой IEEE), достаточно близко к нулю, чтобы "%f" печатало его как 0.000000.

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

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

person Keith Thompson    schedule 26.07.2012

Потому что целое число 10 в двоичном виде выглядит так:

00000000 00000000 00000000 00001010

Все, что делает printf, это берет представление в памяти и пытается представить его как число с плавающей запятой IEEE 754.

Число с плавающей запятой состоит из трех частей (от MSB до LSB):

Знак: 1 бит
Показатель степени: 8 бит
Мантисса: 23 бита

Поскольку целое число 10 равно 1010 в битах мантиссы, это очень крошечное число, которое намного меньше, чем точность по умолчанию для формата с плавающей запятой printf.

person rrhartjr    schedule 26.07.2012
comment
Только если целое число проходит через стек. - person Maxim Egorushkin; 26.07.2012
comment
@rrhartjr вывод для int x=6666666; также равен нулю на идеоне. - person Prateek; 26.07.2012
comment
Я думаю, что разумные соглашения о вызовах будут передавать все вариативные аргументы функций в стеке. Я приветствую любую информацию о том, в чем именно ABI не соответствует действительности. - person Kuba hasn't forgotten Monica; 26.07.2012
comment
Как насчет неразумных условностей? Например, System V ABI для AMD64. - person Bo Persson; 26.07.2012

Результат не определен.

Я просто спрашиваю это с теоретической точки зрения.

Полный крис отличный ответ :

То, что происходит в вашем printf, не определено, но это может быть очень похоже на приведенный ниже код (это зависит от фактической реализации varargs, IIRC).

Отказ от ответственности: следующее объяснение того, что может произойти в случае неопределенного поведения на одной платформе, больше похоже на "как если бы это сработало", чем истинное/действительное описание, которое всегда происходит на всех платформах.

Дайте определение «неопределенное»?

Представьте себе следующий код:

int main()
{
    int i       = 10 ;
    void * pi   = &i ;
    double * pf = (double *) pi ; /* oranges are apples ! */
    double f    = *pf ;

    /* what is the value inside f ? */

    return 0;
}

Здесь, поскольку ваш указатель на double (т. е. pf) указывает на адрес, содержащий целочисленное значение (т. е. i), то, что вы получите, не определено и, скорее всего, является мусором.

Я хочу увидеть, что внутри этого воспоминания!

Если вы действительно хотите увидеть, что может скрываться за этим мусором (при отладке на некоторых платформах), попробуйте следующий код, в котором мы будем использовать объединение для имитации участка памяти, куда мы будем записывать данные типа double или int:

typedef union
{
   char c[8] ; /* char is expected to be 1-byte wide     */
   double f ;  /* double is expected to be 8-bytes wide  */
   int i ;     /* int is expected to be 4-byte wide      */
} MyUnion ;

Поле f и i используются для установки значения, а поле c используется для просмотра (или изменения) памяти, байт за байтом.

void printMyUnion(MyUnion * p)
{
   printf("[%i %i %i %i %i %i %i %i]\n"
      , p->c[0], p->c[1], p->c[2], p->c[3], p->c[4], p->c[5], p->c[6], p->c[7]) ;
}

приведенная выше функция распечатает структуру памяти, байт за байтом.

Приведенная ниже функция напечатает расположение памяти различных типов значений:

int main()
{
   /* this will zero all the fields in the union */
   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   printMyUnion(&myUnion) ; /* this should print only zeroes */
                            /* eg. [0 0 0 0 0 0 0 0] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.i = 10 ;
   printMyUnion(&myUnion) ; /* the representation of the int 10 in the union */
                            /* eg. [10 0 0 0 0 0 0 0] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.f = 10 ;
   printMyUnion(&myUnion) ; /* the representation of the double 10 in the union */
                            /* eg. [0 0 0 0 0 0 36 64] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.f = 3.1415 ;
   printMyUnion(&myUnion) ; /* the representation of the double 3.1415 in the union */
                            /* eg. [111 18 -125 -64 -54 33 9 64] */

   return 0 ;
}

Примечание. Этот код был протестирован на Visual C++ 2010.

Это не означает, что это будет работать так (или вообще) на вашей платформе, но обычно вы должны получить результаты, аналогичные приведенным выше.

В конце концов, мусор - это просто шестнадцатеричный набор данных в памяти, на который вы смотрите, но воспринимаемый как некоторый тип.

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

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

P.S.: Обратите внимание, что поскольку int и double имеют разный размер в байтах, мусор становится еще сложнее, но в основном это то, что я описал выше.

Но я хочу напечатать int как двойной!

Серьезно?

Helios предложил решение .

int main()
{
   int x=10;
   printf("%f",(double)(x));
   return 0;
}

Давайте посмотрим на псевдокод, чтобы увидеть, что передается в printf:

   /* printf("...", [[10 0 0 0]]) ; */
   printf("%i",x);

   /* printf("...", [[10 0 0 0 ?? ?? ?? ??]]) ; */
   printf("%f",x);

   /* printf("...", [[0 0 0 0 0 0 36 64]]) ; */
   printf("%f",(double)(x));

Приведения предлагают другой макет памяти, эффективно изменяя целочисленные данные «10» на двойные данные «10.0».

Таким образом, при использовании "%i" ожидается что-то вроде [[?? ?? ?? ??]], а для первого printf получить [[10 0 0 0]] и правильно интерпретировать его как целое число.

При использовании "%f" ожидается что-то вроде [[?? ?? ?? ?? ?? ?? ?? ??]], и получить на второй printf что-то вроде [[10 0 0 0]], пропущенных 4 байта. Таким образом, 4 последних байта будут случайными данными (вероятно, байтами «после» [[10 0 0 0]], то есть что-то вроде [[10 0 0 0 ?? ?? ?? ??]]

В последнем printf приведение изменило тип и, следовательно, представление памяти на [[0 0 0 0 0 0 36 64]] и printf будет правильно интерпретировать его как двойное.

person paercebal    schedule 26.07.2012

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

person ddyer    schedule 26.07.2012
comment
В x86-64 целые числа и числа с плавающей запятой передаются в совершенно разных регистрах. - person Maxim Egorushkin; 26.07.2012
comment
Кто сказал, что денормалов не должно быть? В любом случае, только маленькие положительные целые числа выглядят денормалами. - person Pascal Cuoq; 26.07.2012

Вы можете привести переменную int следующим образом:

int i = 3;
printf("%f",(float)(i));
person IsKernel    schedule 26.07.2012