2016-03-22 8 views
22

Więc mam dwie funkcje, jedna po prostu rzuca od double do int64_t, pozostałe połączenia std::round:Zrozumienie podwójnej konwersji do int64_t

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; 
} 

one działają poprawnie: cast(3.64) = 3 i round(3.64) = 4. Ale kiedy patrzę na zgromadzenie, wydają się robić to samo. Zastanawiam się, w jaki sposób osiągają różne wyniki?

$ 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> 

nie jestem pewien, jaki jest cel tej callq na linii 14 jest, ale nawet z tym, my_cast i my_round wydają się być po prostu robi cvttsd2si, które moim zdaniem jest konwersja z obcięcia.

Jednak te dwie funkcje, jak już wspomniano wcześniej, wytwarzają różne (poprawnie) wartości tego samego wejścia (słownie 3.64)

Co się dzieje?

+0

Należy zauważyć, że runda (x) = obcięcie (x + 0,5). Podejrzewam, że nie zidentyfikowałeś tutaj wszystkich kodów maszynowych. –

+1

GCC 5.3.0 wywołuje wywołanie 'round' https://gcc.godbolt.org/ @ Cheersandhth.-Alf, które odnosi się tylko do nieujemnych wartości –

+1

' callq 19' to nierozwiązane jeszcze odniesienie do 'std: : round'. Zostanie wypełniony, gdy obiekt zostanie połączony. –

Odpowiedz

18

montażowa jest bardziej przydatna (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 

Jak widać, my_round rozmowy std::round a następnie wykonuje cvttsd2siq instrukcji. Dzieje się tak, ponieważ std::round(double) zwraca double, więc jego wynik musi zostać przekonwertowany na int64_t. I to właśnie robi cvttsd2siq w obu twoich funkcjach.

18

Z g ++ można mieć wyższy poziom Widok tego, co dzieje się za pomocą przełącznika -fdump-tree-optimized:

$ g++ -std=c++1y -c -O3 -fdump-tree-optimized ./round.cpp 

który wytwarza round.cpp.165t.optimized plik:

;; 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; 
} 

Tu różnice są dość jasne (i wywołanie funkcji round rażące).

+1

plus 1 dla opcji '-fdump-tree-optimized'. bardzo przydatne. –

12

Kiedy dumping plik obiektowy z objdump -d, jest to dość ważne, aby dodać opcję -r, który nakazuje narzędziu również zrzucić delokalizacji:

$ 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 

Teraz zauważyć nową linię, która pojawiła. To jest instrukcja relokacji zawarta w pliku obiektowym. Instruuje łącznik, aby dodać odległość między _Z8my_roundd+0x9 i _ZSt5roundd do wartości znalezionej przy przesunięciu 15.

W przypadku przesunięcia 14 e8 jest kod operacji dla wywołania względnego. Następne 4 bajty muszą zawierać przesunięcie względne IP do wywoływanej funkcji (IP w momencie wykonania wskazuje na następną instrukcję). Ponieważ kompilator nie może znać tej odległości, pozostawia ją wypełnioną zerami i wstawia relokację, aby linker mógł ją wypełnić później.

Podczas demontażu bez opcji -r, relokacje są ignorowane, co tworzy iluzję, że funkcja _Z8my_roundd wywołuje połączenie w samym środku.