2009-08-02 10 views
13

Provengo da uno sfondo C#, ma sto imparando C al momento. In C#, quando si vuole segnalare che si è verificato un errore, si genera un'eccezione. Ma cosa fai in C?C: Come si simula un'eccezione?

Ad esempio, si dispone di uno stack con le funzioni push e pop. Qual è il modo migliore per segnalare che lo stack è vuoto durante un pop? Cosa ritorna da quella funzione?

double pop(void) 
{ 
    if(sp > 0) 
     return val[--sp]; 
    else { 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 
} 

s' esempio dalla pagina 77 (codice di cui sopra) K&R restituisce un 0.0. Ma cosa succede se l'utente ha spinto uno 0.0 in precedenza nello stack, come fai a sapere se lo stack è vuoto o se è stato restituito un valore corretto?

risposta

15

comportamento delle eccezioni come in C è compiuto tramite setjmp/longjmp. Tuttavia, quello che vuoi veramente qui è un codice di errore. Se tutti i valori sono potenzialmente a rendere, quindi si consiglia di prendere in un fuori-parametro come un puntatore, e l'uso che per restituire il valore, in questo modo:

int pop(double* outval) 
{ 
     if(outval == 0) return -1; 
     if(sp > 0) 
       *outval = val[--sp]; 
     else { 
       printf("error: stack empty\n"); 
       return -1; 
     } 
     return 0; 
} 

Non è l'ideale, ovviamente, ma quali sono i limiti di C.

Inoltre, se si va su questa strada, è possibile definire costanti simboliche per i propri codici di errore (o usare un po 'di the standard ones), in modo che un utente possa distinguere tra "pila vuota" e "mi ha dato un puntatore nullo, stupido ".

+2

Non sono d'accordo, in qualche modo, perché anche se capisco cosa intendi, non darei a nessuno che provenga da java/C# land l'ipotesi che setjmp/longjmp sia in qualche modo la "soluzione" a "dov'è la mia eccezione?" – Jonke

+0

Si noti che pochissimo codice C usa setjmp/longjmp per ragioni storiche. I codici di errore sono il solito modo C. I codici di errore non sono molto buoni; la mancanza di Eccezioni in C è una delle ragioni principali per cui le lingue più moderne sono migliori. – Nelson

+0

ANSI ha codificato 'setjmp ', quindi è garantito che funzioni (almeno se il compilatore è conforme allo standard), ma lo switch dello stack e la concorrenza hanno non sono stati standardizzati, quindi è ancora un problema scrivere un pacchetto sicuro per C (anche usando asm per fare l'interruttore di contesto) perché un compilatore * potrebbe * (anche se potrebbe essere improbabile) eseguire ottimizzazioni e trasforma che interrompono le ipotesi nel pacchetto di thread. – Jonke

4

Un approccio è specificare che pop() ha un comportamento non definito se lo stack è vuoto. Dovrai quindi fornire una funzione is_empty() che può essere chiamata per controllare lo stack.

Un altro approccio è quello di utilizzare C++, che ha eccezioni :-)

+0

più utilmente per questo caso particolare, C++ ha uno stack proprio lì nella biblioteca :-) –

+0

Ma l'++ std :: stack di pop C la funzione in realtà non fa ciò che l'OP vuole. –

+1

Vero, e capire perché si aggiungerà all'istruzione C++ dell'OP, che è lo scopo principale della domanda :-) Ad ogni modo, avvolgendo una chiamata a 'top()' e 'pop()', restituendo una copia, dà la Lo stesso risultato finale è quello di prendere ciò che l'OP ha e applicare ciò che dici di aver bisogno di una funzione 'empty()'. IYSWIM. –

1

si può restituire un puntatore a doppio:

  • non NULL -> valida
  • NULL -> non valida
+0

i downvotes senza commenti sono inutili, per favore spiegate i vostri downvotes – dfa

+3

Non sono il downvote, ma mi chiedo da dove provenga lo storage di supporto per il puntatore. Se è l'elemento popped, il chiamante deve dereferenziarlo prima di poter inserire un nuovo valore. Se è un 'duplicato statico 'separato, il chiamante deve effettuare il dereferenziamento prima della prossima chiamata a pop. Entrambi causano molti problemi per il codice di chiamata. – RBerteig

+0

Non ho votato meno, ma ho avuto le stesse preoccupazioni. Penso che sia un approccio efficace ma avresti bisogno di cambiare il modo in cui funziona la funzione e memorizza i dati per farlo. – Jon

7

Hai alcune opzioni:

1) Magia valore di errore. Non sempre abbastanza buono, per la ragione che descrivi. Suppongo che in teoria per questo caso tu possa restituire un NaN, ma non lo consiglio.

2) Definire che non è valido per eseguire il pop quando lo stack è vuoto. Quindi il tuo codice presume semplicemente che non sia vuoto (e che non sia definito se lo è), o asserisce.

3) Cambiare la firma della funzione in modo da poter indicare il successo o il fallimento.

int pop(double *dptr) 
{ 
    if(sp > 0) { 
      *dptr = val[--sp]; 
      return 0; 
    } else { 
      return 1; 
    } 
} 

documento come "In caso di successo, restituisce 0 e scrive il valore alla posizione puntata da DPTR On fallimento, restituisce un valore diverso da zero. "

Opzionalmente, è possibile utilizzare il valore restituito o errno per indicare il motivo dell'errore, sebbene per questo particolare esempio vi sia una sola ragione.

4) Passare un oggetto "eccezione" in ogni funzione con il puntatore e scrivere un valore in caso di errore. Il chiamante quindi controlla o meno in base a come usano il valore restituito. Questo è molto simile all'utilizzo di "errno", ma senza che sia un valore a livello di thread.

5) Come altri hanno già detto, implementare le eccezioni con setjmp/longjmp. È fattibile, ma richiede di passare un parametro in più ovunque (l'obiettivo del longjmp da eseguire in caso di errore), oppure nasconderlo in globals. Rende anche la tipica gestione delle risorse in stile C un incubo, perché non puoi chiamare nulla che possa saltare oltre il tuo livello di stack se hai in mano una risorsa di cui sei responsabile per la liberazione.

+0

+1: per il punto n. –

10

È possibile creare un sistema di eccezione su longjmp/setjmp: Exceptions in C with Longjmp and Setjmp. In realtà funziona abbastanza bene, e l'articolo è una buona lettura. Ecco come il codice potrebbe apparire come se è stato utilizzato il sistema di eccezione dal articolo collegato:

TRY { 
    ... 
    THROW(MY_EXCEPTION); 
    /* Unreachable */ 
    } CATCH(MY_EXCEPTION) { 
    ... 
    } CATCH(OTHER_EXCEPTION) { 
    ... 
    } FINALLY { 
    ... 
    } 

E 'incredibile quello che si può fare con un po' di macro, giusto? È altrettanto sorprendente quanto sia difficile capire cosa diavolo sta succedendo se non sai già cosa fanno i macro.

longjmp/setjmp sono portatili: C89, C99 e POSIX.1-2001 specificare setjmp().

Si noti, tuttavia, che le eccezioni implementate in questo modo avranno ancora alcune limitazioni rispetto alle eccezioni "reali" in C# o C++. Un grosso problema è che solo il tuo codice sarà compatibile con questo sistema di eccezione. Poiché non esiste uno standard stabilito per le eccezioni in C, le librerie di sistema e di terze parti non interagiranno in modo ottimale con il proprio sistema di eccezioni homegrown. Tuttavia, a volte questo può rivelarsi un utile trucco.

Non è consigliabile utilizzarlo nel codice serio con cui altri programmatori devono lavorare. È troppo facile spararti ai piedi con questo se non sai esattamente cosa sta succedendo. Threading, gestione delle risorse e gestione dei segnali sono aree problematiche che i programmi non giocattolo incontreranno se si tenta di utilizzare "eccezioni" longjmp.

+4

Realmente ho costruito qualcosa come questo in C++ prima che le eccezioni diventassero ampiamente disponibili. Ho persino implementato la propria forma di pila che si svolge. Fortunatamente, sono tornato in me prima che lo usassimo nel codice di produzione. –

+0

@Neil: Mi è piaciuta la seconda parte di questa frase :-) – jeroenh

+1

"Fortunatamente, sono tornato in me". Complimenti. Symbian ha fatto la stessa cosa che hai fatto, fino al punto in cui sei tornato in te, e loro hanno spedito. 10+ anni dopo, hanno ancora NewLC ovunque ... –

2

Non vi sono equivalenti alle eccezioni nella scala C. È necessario progettare la firma della funzione per restituire le informazioni di errore, se questo è ciò che si desidera.

I meccanismi disponibili in C sono:

goto
  • non-locali con setjmp/longjmp
  • Segnali

Tuttavia, nessuno di questi ha semantica lontanamente simile a C# (o C++) eccezioni .

3

In casi come questo, fai di solito uno dei

  • lasciare al chiamante. per esempio. spetta al chiamante sapere se è sicuro pop() (ad esempio chiama una funzione stack-> is_empty() prima di aprire lo stack), e se il chiamante incasina, è colpa sua e buona fortuna.
  • Segnala l'errore tramite un parametro out o il valore di ritorno.

ad es.si sia non

double pop(int *error) 
{ 
    if(sp > 0) { 
     return val[--sp]; 
     *error = 0; 
    } else { 
    *error = 1; 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 

}

o

int pop(double *d) 
{ 
    if(sp > 0) { 
     *d = val[--sp]; 
     return 0; 
    } else { 
    return 1; 

    } 
} 
0

ci sono già alcune buone risposte qui, volevo solo dire che qualcosa di simile a "eccezione", può essere fatto con l'uso di una macro, come è stato fatto nel fantastico MinUnit (questo restituisce solo l '"eccezione" alla funzione chiamante).

1

1) Si restituisce un valore flag per mostrare che non è riuscito o si utilizza una sintassi TryGet in cui il ritorno è un valore booleano per il successo mentre il valore viene passato attraverso un parametro di output.

2) Se questo è in Windows, esiste una forma di eccezioni di puro livello del sistema operativo, chiamata Gestione delle eccezioni strette, che utilizza la sintassi come "_try". Lo dico, ma non lo consiglio per questo caso.

4

Questo in realtà è un perfetto esempio dei mali del tentativo di sovraccaricare il tipo di ritorno con valori magici e un semplice design dell'interfaccia discutibile.

Una soluzione che potrebbe utilizzare per eliminare l'ambiguità (e quindi la necessità di "eccezione come comportamento") nell'esempio è quello di definire un corretto tipo di ritorno:

struct stack{ 
    double* pData; 
    uint32 size; 
}; 

struct popRC{ 
    double value; 
    uint32 size_before_pop; 
}; 

popRC pop(struct stack* pS){ 
    popRC rc; 
    rc.size=pS->size; 
    if(rc.size){ 
     --pS->size; 
     rc.value=pS->pData[pS->size]; 
    } 
    return rc; 
} 

Uso naturalmente è:

popRC rc = pop(&stack); 
if(rc.size_before_pop!=0){ 
    ....use rc.value 

questo accade tutto il tempo, ma in C++ per evitare tali ambiguità uno di solito solo restituisce un

std::pair<something,bool> 
.210

dove il bool è un indicatore di successo - un'occhiata ad alcuni dei:

std::set<...>::insert 
std::map<...>::insert 

In alternativa aggiungere un double* all'interfaccia e restituire un codice di ritorno (! N UNOVERLOADED), dicono un enum che indica il successo.

Ovviamente non è necessario restituire la dimensione nella struttura popRC. Avrebbe potuto essere

enum{FAIL,SUCCESS}; 

Ma poiché dimensioni potrebbe essere utilizzato come un suggerimento utile alla pop'er si potrebbe anche usarlo.

BTW, ho vivamente d'accordo che l'interfaccia pila struct dovrebbe avere

int empty(struct stack* pS){ 
    return (pS->size == 0) ? 1 : 0; 
} 
0

setjmp, longjmp, e le macro. E 'stato fatto un numero illimitato di volte — l'implementazione più vecchia che io conosca è di Eric Roberts e Mark vanderVoorde — ma quella che uso attualmente fa parte di Dave Hanson C Interfaces and Implementations ed è gratuita da Princeton.

1

Qualcosa che nessuno ha ancora detto, è piuttosto brutto però:

int ok=0; 

do 
{ 
    /* Do stuff here */ 

    /* If there is an error */ 
    break; 

    /* If we got to the end without an error */ 
    ok=1; 

} while(0); 

if (ok == 0) 
{ 
    printf("Fail.\n"); 
} 
else 
{ 
    printf("Ok.\n"); 
} 
Problemi correlati