Come posso restituire un errore da una funzione booleana in C?

2

Sto lavorando ad un piccolo programma per animali domestici in C, dove ho una tavola da gioco che consiste in un mazzo di quadrati:

typedef struct _square {
    bool checked;
} Square;

typedef struct _board {
    Square *squares;
    uint8_t width;
    uint8_t height;
} Board;

Non voglio che queste strutture siano visibili al pubblico, quindi, invece, voglio scrivere una semplice funzione che controlli se un quadrato è controllato o meno, chiamato square_is_checked . Ha senso che il tipo restituito sia un bool (sto assumendo C99 qui).

bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
    return b->squares[(uint16_t)row * b->height + col].checked;
}

Tuttavia, è possibile che un puntatore NULL o una riga e una colonna che non esistono possano essere passati come argomenti. Quindi voglio aggiungere alcuni semplici controlli:

bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
    if (!b) { return ???; }
    if (row >= b->height || col >= b->width) { return ???; }
    return b->squares[(uint16_t)row * b->height + col].checked;
}

Tuttavia, un bool è true o false - non c'è spazio per la gestione degli errori. Qual è la migliore pratica qui (ad es. Devo riscrivere questa funzione per passare un puntatore a bool )?

    
posta tonysdg 11.09.2017 - 04:00
fonte

4 risposte

8

Non si tratta in realtà di come restituire bool da una funzione in C. Il tuo codice lo fa già.

La domanda è, è bool il tipo di dati corretto quando l'insieme di possibili valori è maggiore di true e false . Potrebbe essere l'argomento di una lunga discussione filosofica, ma poiché si tratta di un progetto personale, non è necessario preoccuparsi della propria decisione in un contesto più ampio. In una squadra, dove il codice esistente potrebbe già avere un'opinione su situazioni come questa, potrebbe essere presa una decisione per te.

Quindi in questo caso ci sono molte possibili risposte. Alcune opzioni:

  • Mantieni il tipo di ritorno bool e usa false per errori di input come puntatori nulli o valori non validi ( row > height , ecc.)
  • Utilizza un enum per rappresentare CHECKED , UNCHECKED , UNKNOWN ecc. al posto di un booleano. Molte librerie C lo fanno efficacemente restituendo 0 (ad esempio INVALID_SOCKET ) per indicare un errore (o talvolta -1).
  • Convalidare gli input altrove per assicurarsi che questa funzione possa sempre restituire correttamente un booleano significativo quando viene chiamato.
risposta data 11.09.2017 - 04:51
fonte
6

Alcune altre opzioni includono il passaggio di un puntatore a bool e la restituzione di un valore di errore. Qualcosa del genere:

typedef enum square_err_t {
    ERROR_NONE = 0,
    ERROR_BAD_PTR,
    ERROR_BAD_ROW,
    ERROR_BAD_COL
} square_err_t;

square_err_t square_is_checked(Board* b, uint8_t row, uint8_t col, bool* is_checked)
{
    ...
}

Poiché si tratta di un progetto personale, potresti essere in grado di farla franca eseguendo i tuoi controlli come precondizioni tramite assert :

bool square_is_checked(Board* b, uint8_t row, uint8_t col)
{
    assert(b != NULL);
    assert(col < b->width);
    assert(row < b->height);
    ...etc.
}

Questo catturerà errori durante lo sviluppo, ma i controlli verranno compilati nel rilascio.

Se il tuo compilatore o altri strumenti lo supportano, potresti essere in grado di annotare le tue funzioni con annotazioni nullability . (Questo esempio è per Swift, ma una ricerca rapida mostra gli strumenti che li supportano disponibili anche per altri linguaggi.) Questi ti permettono di contrassegnare gli argomenti di una funzione come nonnull , il che significa che l'argomento non può essere NULL. Questo catturerà alcune potenziali chiamate in fase di compilazione piuttosto che in fase di esecuzione e ti consentirà di correggerle prima della spedizione.

    
risposta data 11.09.2017 - 05:57
fonte
2

Per te è fondamentale prendere una decisione:

Un input non valido è una violazione del contratto o è previsto?

Il chiamante dipende già dal chiamante per dare un puntatore a Board , e quello giusto a quello. Perché vuoi caso speciale l'input non valido NULL , non sono tutti gli errori logici del programmatore?

Una posizione fuori bordo della scheda può essere discussa molto di più:

  1. Si tratta di un errore logico commesso dal programmatore?
  2. La convalida non è stata inserita dall'utente?
  3. C'è una continuazione logica della scheda fornita?

Ci sono tre modi per reagire agli errori logici:

  1. Panic. assert() in debug-builds (sii generoso con assert, dato che è un no-op in release), abort() in release. A seconda della gravità dell'errore, potrebbero essere idonei in lingue moderatamente sicure con eccezioni che generano un'eccezione non controllata.
  2. Lascia che i dadi cadano dove vogliono. Questo è ciò che significa Comportamento indefinito e consente di gran lunga la massima efficienza.
  3. Prova a sostituire le impostazioni predefinite sensibili e confonderlo o restituire un errore. Nelle lingue con eccezioni, le eccezioni potrebbero essere il modo in cui restituisci un errore.
risposta data 11.09.2017 - 12:59
fonte
-3

Forse dai un'occhiata a man errno e usa una delle due strategie discusse lì. O int square_is_checked (etc) e return -1 per errori, oppure imposta errno per segnalare errori, come discusso dalla pagina man. (Personalmente, di solito vado con -1 in questo tipo di situazioni. Puoi utilizzare int proprio come il tuo bool .)

    
risposta data 11.09.2017 - 04:32
fonte

Leggi altre domande sui tag