Подтвердить что ты не робот

Понимание преобразования 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)

Что происходит?

4b9b3361

Ответ 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 делает вызов посередине самой.