Преобразование типа указателя С
Имею код:
int a = 1065353216;
int* pa = &a;
printf("%d", *((float*)pa)); // Выводит 0
Почему на выводе 0? Я так понимаю, что sizeof(int*) == sizeof(float*), то есть рассматривается 4 байта информации, но интерпретируется бинарное представление этой информации как целочисленное, тогда на выводе должно быть 1065353216. В чем проблема?
P. S. бинарное представление:
1065353216: 00111111 10000000 00000000 00000000 (1.0 для IEEE754 for float)
Ответы (3 шт):
по сути ваш код делает следующее:
int a = 1065353216;
int* pa = &a;
float* pb = (float*)pa;
float b = *pb;
printf("%d", b);
т.е. вы пытаетесь сделать следующее:
printf("%d", 1.0f);
и 0 выдает только потому, что вы передаёте не int, т.е. это такой способ просигнализировать об ошибке
Warning C4477 'printf' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'float'
несоотвествие типов приводит к неопределённому поведению. gcc с printf преобразует тип float в double, затем печатает.
код :
#include <stdio.h>
int main(){
int a = 1065353216;
int* pa = &a;
printf("%d\n", *((float*)pa)); // Выводит 0
printf("%d\n", *pa);
}
ассемблер :
.LC1:
.string "%d\n"
main:
push rax
movsd xmm0, QWORD PTR .LC0[rip] ; здесь float -> double
mov edi, OFFSET FLAT:.LC1
mov al, 1
call printf
mov esi, 1065353216 ; целые числа передаются через esi
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call printf
xor eax, eax
pop rdx
ret
.LC0:
.long 0
.long 1072693248 ; тут float константа числа
вывод :
-490767160
1065353216
ссылка :
Так как в первом printf компилятор не загрузил число в регистр esi будет печататься случайное число. У разных компиляторов всё по разному.
Во-первых, поведение вашей программы неопределенно, так как она нарушает strict aliasing. Нельзя обращаться к объекту типа int как к объекту типа float.
Во-вторых, поведение вашей программы неопределенно, так как передаваемый в функцию printf аргумент не соответствует используемому спецификатору преобразования. Функция printf ожидает получить int а вы передаёте аргумент типа double.
В-третьих, даже если два предыдущих UB отработают так, как и задумано, вы не учитываете default argument promotions. Аргумент типа float преобразуется к типу double. А значит в строке printf("%d", *((float*)pa)); функция printf на самом деле получает значение типа double и пытается вывести первые четыре байта восьмибайтного аргумента (при условии, что тип int занимает 4 байта, а тип double занимает 8 байт).
Если переданное значение типа double равно 1.0, то все 52 бита мантиссы 64 битного числа с плавающей точкой равны нулю. А значит первые четыре байта скорее всего также будут равны нулю (а могут быть и не равны нулю, если байты, составляющие объект типа double хранятся в каком-то необычном порядке).
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
int main(void)
{
float a = 1.0f;
double b = a;
uint32_t a32, b32;
uint64_t b64;
memcpy(&a32, &a, sizeof(a32));
memcpy(&b64, &b, sizeof(b64));
memcpy(&b32, &b, sizeof(b32));
printf("a32 = %" PRIu32 "\n", a32);
printf("b64 = %" PRIu64 "\n", b64);
printf("b32 = %" PRIu32 "\n", b32);
return 0;
}
Вывод:
a32 = 1065353216
b64 = 4607182418800017408
b32 = 0
Таким образом, ваш код при "щадящей" обработке UB эквивалентен следующему:
printf("0");