float - long c++
Entendiendo la conversión de doble a int64_t (3)
Al volcar un archivo de objeto con objdump -d
, es muy importante agregar la opción -r
, que ordena a la utilidad que también vuelque las reubicaciones:
$ objdump -dr round.o
...
0000000000000010 <_Z8my_roundd>:
10: 48 83 ec 28 sub $0x28,%rsp
14: e8 00 00 00 00 callq 19 <_Z8my_roundd+0x9>
15: R_X86_64_PC32 _ZSt5roundd
19: 48 83 c4 28 add $0x28,%rsp
1d: f2 48 0f 2c c0 cvttsd2si %xmm0,%rax
Ahora, note la nueva línea que apareció. Esa es una instrucción de reubicación incorporada en el archivo objeto. Le indica al enlazador que agregue una distancia entre _Z8my_roundd+0x9
y _ZSt5roundd
al valor encontrado en el desplazamiento 15.
El e8
en el desplazamiento 14 es el código de operación para la llamada relativa. Los siguientes 4 bytes deben contener el desplazamiento relativo a la IP a la función que se está llamando (la IP en el momento de la ejecución que apunta a la siguiente instrucción). Debido a que el compilador no puede conocer esa distancia, la deja llena de ceros e inserta una reubicación para que el enlazador pueda llenarla más tarde.
Al desmontar sin la opción -r
, las reubicaciones se ignoran, y eso crea la ilusión de que la función _Z8my_roundd
realiza una llamada en el centro de sí misma.
Así que tengo dos funciones, una solo int64_t
de double
a int64_t
, la otra llama std::round
:
std::int64_t my_cast(double d)
{
auto t = static_cast<std::int64_t>(d);
return t;
}
std::int64_t my_round(double d)
{
auto t = std::round(d);
return t;
}
Funcionan correctamente: cast(3.64)
= 3
y round(3.64)
= 4
. Pero, cuando miro la asamblea, parecen estar haciendo lo mismo. Así que me pregunto cómo obtienen resultados diferentes?
$ g++ -std=c++1y -c -O3 ./round.cpp -o ./round.o
$ objdump -dS ./round.o
./round.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_Z7my_castd>:
0: f2 48 0f 2c c0 cvttsd2si %xmm0,%rax
5: c3 retq
6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
d: 00 00 00
0000000000000010 <_Z8my_roundd>:
10: 48 83 ec 08 sub $0x8,%rsp
14: e8 00 00 00 00 callq 19 <_Z7my_castd+0x19> <========!!!
19: 48 83 c4 08 add $0x8,%rsp
1d: f2 48 0f 2c c0 cvttsd2si %xmm0,%rax
22: c3 retq
Disassembly of section .text.startup:
0000000000000030 <_GLOBAL__sub_I__Z7my_castd>:
30: 48 83 ec 08 sub $0x8,%rsp
34: bf 00 00 00 00 mov $0x0,%edi
39: e8 00 00 00 00 callq 3e <_GLOBAL__sub_I__Z7my_castd+0xe>
3e: ba 00 00 00 00 mov $0x0,%edx
43: be 00 00 00 00 mov $0x0,%esi
48: bf 00 00 00 00 mov $0x0,%edi
4d: 48 83 c4 08 add $0x8,%rsp
51: e9 00 00 00 00 jmpq 56 <_Z8my_roundd+0x46>
No estoy seguro de cuál es el propósito de ese callq
en la línea 14
, pero, aun así, my_cast
y my_round
parecen estar simplemente haciendo un cvttsd2si
que, creo, es conversión con truncamiento.
Sin embargo, las dos funciones, como mencioné anteriormente, producen valores diferentes (correctos) en la misma entrada (por ejemplo, 3.64
)
¿Que esta pasando?
Con g ++ puede tener una vista de nivel superior de lo que está sucediendo con el conmutador -fdump-tree-optimized
:
$ g++ -std=c++1y -c -O3 -fdump-tree-optimized ./round.cpp
Eso produce un archivo round.cpp.165t.optimized
:
;; Function int64_t my_cast(double) (_Z7my_castd, funcdef_no=224, decl_uid=4743$
int64_t my_cast(double) (double d)
{
long int t;
<bb 2>:
t_2 = (long int) d_1(D);
return t_2;
}
;; Function int64_t my_round(double) (_Z8my_roundd, funcdef_no=225, decl_uid=47$
int64_t my_round(double) (double d)
{
double t;
int64_t _3;
<bb 2>:
t_2 = round (d_1(D));
_3 = (int64_t) t_2;
return _3;
}
Aquí las diferencias son bastante claras (y la llamada a la función de round
es evidente).
El resultado del ensamblaje es más útil ( g++ ... -S && cat round.s
):
...
_Z7my_castd:
.LFB225:
.cfi_startproc
cvttsd2siq %xmm0, %rax
ret
.cfi_endproc
...
_Z8my_roundd:
.LFB226:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
call round <<< This is what callq 19 means
addq $8, %rsp
.cfi_def_cfa_offset 8
cvttsd2siq %xmm0, %rax
ret
.cfi_endproc
Como puede ver, my_round
llama a std::round
y luego ejecuta la instrucción cvttsd2siq
. Esto se debe a que std::round(double)
devuelve el double
, por lo que su resultado aún debe convertirse a int64_t
. Y eso es lo que hace cvttsd2siq
en tus dos funciones.