2011-09-02 7 views
9
#include <stdio.h> 

char toUpper(char); 

int main(void) 
{ 
    char ch, ch2; 
    printf("lowercase input : "); 
    ch = getchar(); 
    ch2 = toUpper(ch); 
    printf("%c ==> %c\n", ch, ch2); 

    return 0; 
} 

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
} 

Nella funzione toUpper, il tipo restituito è char, ma non c'è "return" in toUpper(). E compilare il codice sorgente con gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4), fedora-14.Perché e in che modo GCC compila una funzione con un'istruzione di reso mancante?

Ovviamente, viene emesso un avviso: "avviso: il controllo raggiunge la fine della funzione non vuota", ma funziona bene.

Cosa è successo in quel codice durante la compilazione con gcc? Voglio ottenere una risposta solida in questo caso. Grazie :)

+5

odora di comportamento non definito. – ThiefMaster

+2

@ThiefMaster: It ** è ** UB. È semplicemente fortunato che il registro in cui normalmente viene inserito il valore di ritorno viene utilizzato anche per la sottrazione. –

+0

Grazie per tutti :) – harrison

risposta

19

Che cosa è successo per voi è che quando il programma C è stato compilato in linguaggio assembly, la funzione toupper finito in questo modo, forse:

_toUpper: 
LFB4: 
     pushq %rbp 
LCFI3: 
     movq %rsp, %rbp 
LCFI4: 
     movb %dil, -4(%rbp) 
     cmpb $96, -4(%rbp) 
     jle  L8 
     cmpb $122, -4(%rbp) 
     jg  L8 
     movzbl -4(%rbp), %eax 
     subl $32, %eax 
     movb %al, -4(%rbp) 
L8: 
     leave 
     ret 

La sottrazione di 32 è stata effettuata nel registro% eax. E nella convenzione di chiamata x86, questo è il registro in cui ci si aspetta che il valore restituito sia! Quindi ... sei stato fortunato.

Ma si prega di prestare attenzione alle avvertenze. Sono lì per un motivo!

+0

+1 per mostrare come asm –

+1

Questo è esattamente quello che indovinerei: eax è usato, contiene il risultato dell'operazione e eax è il valore di ritorno. Ma ragazzo, è difficile leggere l'assembly GNU quando sei abituato allo stile Intel. –

+0

Ora sapevo qual è questo problema. Ho visto l'avvertimento, ma mi chiedo solo. Grazie :) – harrison

7

Dipende dallo Application Binary Interface e da quali registri vengono utilizzati per il calcolo.

E.g. su x86, il primo parametro di funzione e il valore di ritorno sono memorizzati in EAX e quindi gcc lo utilizza molto probabilmente per memorizzare anche il risultato del calcolo.

+0

Ho letto l'ABI e la convenzione di chiamata. Grazie :) – harrison

1

Non posso dirvi le specifiche della vostra piattaforma perché non lo conosco, ma c'è una risposta generale al comportamento che si vede.

Quando la funzione che ha un ritorno è compilata, il compilatore utilizzerà una convenzione su come restituire quei dati. Potrebbe essere un registro di macchina o una posizione di memoria definita, ad esempio tramite uno stack o qualsiasi altra cosa (anche se in genere vengono utilizzati registri di macchina). Il codice compilato può anche usare quella posizione (registro o altro) mentre si fa il lavoro della funzione.

Se la funzione non restituisce nulla, il compilatore non genererà il codice che riempie esplicitamente tale posizione con un valore di ritorno. Comunque, come ho detto sopra, può usare quella posizione durante la funzione. Quando scrivi un codice che legge il valore di ritorno (ch2 = toUpper(ch);), il compilatore scriverà il codice che usa la sua convenzione su come recuperare quel ritorno dalla posizione convenzionale. Per quanto riguarda il codice del chiamante, leggerà quel valore dalla posizione, anche se non è stato scritto esplicitamente lì. Quindi ottieni un valore.

Ora osservate l'esempio di @ Ray, il compilatore ha utilizzato il registro EAX per archiviare i risultati dell'operazione di alloggiamento superiore. Succede, questa è probabilmente la posizione in cui vengono scritti i valori di ritorno. Sul lato chiamante ch2 è caricato con il valore che è in EAX - quindi un ritorno fantasma. Questo è vero solo per la gamma di processori x86, in quanto su altre architetture il compilatore può utilizzare uno schema completamente diverso nel decidere come organizzare la convenzione

Tuttavia, i buoni compilatori cercheranno di ottimizzare in base al set di condizioni locali, conoscenza di codice, regole e euristica. Quindi una cosa importante da notare è che questa è solo fortuna che funzioni. Il compilatore potrebbe ottimizzare e non fare questo o altro - non dovresti rispondere al comportamento.

+0

-1 suona come un biscotto della fortuna cinese ... sii più specifico – Quamis

+0

Che cosa vorresti che specificassi per favore? –

+0

ritirato il mio -1 :) – Quamis

2

In sostanza, c viene inserito nel punto che in seguito deve essere riempito con il valore restituito; poiché non viene sovrascritto dall'uso di return, finisce come valore restituito.

Nota che basarsi su questo (in C o in qualsiasi altra lingua in cui questa non è una funzione di linguaggio esplicita, come Perl), è una Bad Idea ™. All'estremo.

+0

è% eax register. L'ho preso – harrison

2

Una cosa che è importante capire è che raramente è un errore diagnosticabile omettere un'istruzione di ritorno. Considerate questa funzione:

int f(int x) 
{ 
    if (x!=42) return x*x; 
} 

Finché non chiami con un argomento di 42, un programma che contiene questa funzione è perfettamente valido C e non invoca alcuna comportamento non definito, nonostante il fatto che essa sarebbe invoke UB se hai chiamato f(42) e successivamente hai tentato di utilizzare il valore restituito.

In questo modo, mentre è possibile che un compilatore fornisca un'euristica di avviso per le dichiarazioni di reso mancanti, è impossibile farlo senza falsi positivi o falsi negativi. Questa è una conseguenza dell'impossibilità di risolvere il problema dell'arresto.

+2

+1 Bello per chiarire questo. L'errore di compile time di Java e Ada sui ritorni mancanti viene emesso ogni volta che rileva che esiste _esiste_ un percorso attraverso la funzione che può uscire senza un'istruzione return, non che tale percorso verrà preso con certezza. –

0

Non ci sono variabili locali, quindi il valore in cima alla pila alla fine della funzione sarà il parametro c. Il valore in cima alla pila all'uscita è il valore di ritorno. Quindi qualunque cosa c vale, questo è il valore di ritorno.

+0

Grazie facili istruzioni – harrison

0

Si dovrebbe tenere presente che tale codice potrebbe bloccarsi a seconda del compilatore. Ad esempio, clang genera un'istruzione ud2 alla fine di tale funzione e la tua app si bloccherà in fase di esecuzione.

0

ho cercato un piccolo programma:

#include <stdio.h> 
int f1() { 
} 
int main() { 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
} 

Risultato:

TEST: < 1>

TEST: < 10>

TEST: < 11>

TEST: < 11>

TEST: < 11>

Ho usato il compilatore gcc mingw32-, quindi ci potrebbe essere diferences.

Si potrebbe semplicemente giocare e provare ad es. una funzione char. Finché non si utilizza il valore del risultato, funzionerà benissimo.

#include <stdio.h> 
char f1() { 
} 
int main() { 
    f1(); 
} 

Ma io consiglio di impostare la funzione void o restituire un valore di ritorno.

La vostra funzione sembrano avere bisogno di un ritorno:

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
    return c; 
} 
Problemi correlati