Понимание преобразования double в int64_t
Итак, у меня есть две функции: одна просто отбрасывает от double
до int64_t
, другие вызовы 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;
}
Они работают правильно: cast(3.64)
= 3
и round(3.64)
= 4
. Но, когда я смотрю на собрание, они, похоже, делают то же самое. Так интересно, как они получают разные результаты?
$ 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>
Я не уверен, для чего предназначена цель этого callq
на линии 14
, но даже при этом my_cast
и my_round
кажутся просто выполняющими cvttsd2si
, которые, я считаю, преобразование с усечением.
Однако две функции, как я упоминал ранее, создают разные (правильные) значения на одном и том же входе (скажем, 3.64
)
Что происходит?
Ответы
Ответ 1
Результат сборки более полезен (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
Как вы можете видеть, my_round
вызывает std::round
, а затем выполняет команду cvttsd2siq
. Это связано с тем, что std::round(double)
возвращает double
, поэтому его результат все равно должен быть преобразован в int64_t
. И это то, что cvttsd2siq
делает в обеих ваших функциях.
Ответ 2
С g++ вы можете иметь представление более высокого уровня о том, что происходит с помощью переключателя -fdump-tree-optimized
:
$ g++ -std=c++1y -c -O3 -fdump-tree-optimized ./round.cpp
Это создает файл 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;
}
Здесь различия совершенно ясны (и вызов функции round
glaring).
Ответ 3
При сбросе объектного файла с помощью objdump -d
очень важно добавить опцию -r
, которая заставляет утилиту также выгружать репозиции:
$ 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
Теперь обратите внимание на новую строку, которая появилась. Это команда перемещения, воплощенная в объектный файл. Он инструктирует компоновщик добавить расстояние между _Z8my_roundd+0x9
и _ZSt5roundd
до значения, найденного со смещением 15.
e8
при смещении 14 - это код операции для относительного вызова. Следующие 4 байта должны содержать относительное смещение IP к вызываемой функции (IP в момент выполнения, указывая на следующую инструкцию). Поскольку компилятор не может знать это расстояние, он оставляет его заполненным нулями и вставляет перемещение, чтобы компоновщик мог его заполнить позже.
При разборке без опции -r
перемещение игнорируется и создает иллюзию, что функция _Z8my_roundd
делает вызов посередине самой.