2016-03-22 22 views
22

Così ho due funzioni, si getta solo double-int64_t, le altre chiamate std::round:doppia intesa per int64_t conversione

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

Essi funzionano correttamente: cast(3.64) = 3 e round(3.64) = 4. Ma quando guardo l'assemblea, sembrano fare la stessa cosa. Quindi mi chiedo come ottengono risultati diversi?

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

io non sono sicuro di quello che la finalità di tale callq on line 14 è per, ma, anche con quello, my_cast e my_round sembrano essere solo facendo un cvttsd2si che, credo è la conversione con troncamento.

Tuttavia, le due funzioni, come ho già detto in precedenza, producono diversi valori (corrette) sullo stesso ingresso (diciamo 3.64)

Che cosa sta accadendo?

+0

Si noti che round (x) = trunc (x + 0,5). Sospetto che tu non abbia identificato correttamente tutto il codice della macchina qui. –

+1

GCC 5.3.0 produce una chiamata a 'round' https://gcc.godbolt.org/ @ Cheersandhth.-Alf è vero solo per valori non negativi –

+1

' callq 19' è un riferimento non ancora risolto, ma 'std: : round'. Sarà compilato quando l'oggetto è collegato. –

risposta

18

uscita Assemblea è più utile (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 

Come si può vedere, my_round chiamate std::round e poi esegue cvttsd2siq istruzioni. Questo perché std::round(double) restituisce double, quindi il suo risultato deve ancora essere convertito in int64_t. Ed è quello che sta facendo cvttsd2siq in entrambe le tue funzioni.

18

Con g ++ si può avere una vista più alto livello di ciò che sta accadendo con l'interruttore -fdump-tree-optimized:

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

che produce un file 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; 
} 

Qui le differenze sono abbastanza chiaro (e la chiamare la funzione round abbagliante).

+1

più 1 per l'opzione '-fdump-tree-optimize'. molto utile. –

12

Quando scaricate un file oggetto con objdump -d, è molto importante per aggiungere l'opzione -r, che comanda l'utility per eseguire il dump anche delocalizzazioni:

$ 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 

Ora, notare la nuova linea che è apparso. Questa è un'istruzione di trasferimento incorporata nel file oggetto. Essa indica al linker per aggiungere una distanza tra _Z8my_roundd+0x9 e _ZSt5roundd al valore trovato all'offset 15.

Il e8 all'offset 14 è il codice di operazione per la chiamata relativa. I seguenti 4 byte devono contenere l'offset relativo IP alla funzione chiamata (l'IP al momento dell'esecuzione che punta all'istruzione successiva). Poiché il compilatore non può sapere che distanza, lo lascia riempito con zeri e inserisce una rilocazione in modo che il linker possa riempirlo in un secondo momento.

Durante lo smontaggio senza l'opzione -r, le delocalizzazioni vengono ignorate e ciò crea l'illusione che la funzione _Z8my_roundd effettui una chiamata nel mezzo di se stessa.