Tipo di opzione di Poor-man vs valore sentinella in C

6

Sto discutendo sul merito (bikehedding?) di due diversi approcci a un semplice problema.

Questa impostazione è numerica, ma potrebbe non esistere, quindi le opzioni sono:

  • Non esiste
  • 0
  • 1
  • 2
  • 3

Il linguaggio di programmazione in uso è C, quindi ci sono due modi ovvi per codificarlo in un byte. L'opzione 1 consiste nell'utilizzare i valori 0, 1, 2, 3 e 0xff per indicare che il valore non è definito. L'opzione 2 è di usare bit0 per specificare se il valore è definito ei bit 1 e 2 per definire il valore (quando bit0 è 1).

La mia sensazione personale è che l'opzione 2 sia superiore perché la segnalazione in-band è una probabile fonte di errori. Sebbene riconosca che lo spostamento / il mascheramento potrebbe rendere l'opzione 2 un po 'più lenta.

Quale opzione preferiresti e perché? Oppure suggerisci un'altra opzione.

    
posta Dave 21.10.2016 - 02:38
fonte

3 risposte

6

Preferirei la prima opzione perché può essere facilmente rappresentata come enum con nomi ben definiti e renderebbe il codice più leggibile. La seconda opzione potrebbe anche essere resa leggibile, con una funzione wrapper (ad esempio convertIntegerToOptionEnum ), ma sembra una complicazione inutile.

L'unica situazione in cui andrei per l'opzione 2 è dove abbiamo ottimizzazioni di memoria / rete molto strette e stiamo facendo le strutture con bitfield ( link ), lasciando i restanti 5 bit per altre informazioni utili. Tali situazioni sono tuttavia poche e lontane.

Tuttavia, mi manca la tua preoccupazione di segnalazione in-band: forse la descrizione del caso d'uso generale farebbe pendere l'equilibrio in quella direzione?

P.S. Ho imparato il termine "bikehedding" - grazie!

    
risposta data 21.10.2016 - 03:32
fonte
1

Non è chiaro dalla tua domanda, ma sembra che la funzione restituisca char - è corretta?

Ancora più importante, i valori 0, 1, 2 e 3 devono essere interpretati come numeri o sono solo rappresentazioni di un altro dominio semantico (enumerazioni)? Questo è fondamentale per fare la scelta giusta. Se vogliono rappresentare solo quattro scelte distinte, dovresti utilizzare enum .

Se no, allora penso che entrambe le scelte siano ugualmente valide. Entrambi sono una forma di segnalazione in-band, a proposito. In C è comune, e non c'è molta alternativa. C'è un "trucco" che potresti fare per emulare un tipo di opzione:

struct option {
    char c;
    bool is_valid;
}

Questo non è molto nello spirito di C, però.

    
risposta data 21.10.2016 - 04:40
fonte
0

Un approccio comune è usare un intero (firmato). Tutto ciò che è negativo è "indefinito". Questo ha un vantaggio rispetto all'utilizzo di 0xFF come valore sentinella poiché è tecnicamente un numero positivo valido se si utilizza un char senza segno. Puoi definire il sentinella come -1 oppure controllare che il tuo valore sia > 0.

#include <stdint.h>
#define UNDEFINED -1
uint8_t i = UNDEFINED;
...
i = some_value;
if (i>=0) { do something interesting } else { undefined }

Da una prospettiva di programmazione difensiva hai ancora un problema. Devi sempre controllare che la variabile sia > = 0 prima di usarla. È possibile ottenere un bug gnarly utilizzando un valore negativo in un calcolo.

L'altra soluzione di usare una struttura per contenere un bit "valido" ha lo stesso problema. Per garantire la sicurezza, devi sempre accedere alla struttura dei dati tramite un'API per evitare di fare riferimento accidentalmente al valore dei dati quando la bandiera dice che non è valido.

typedef struct {
  unsigned char valid:1;
  unsigned char value:7;
} myType;
myType i ={0,0}; // initialize to invalid
...
i.valid=1;
i.value=some_value;
...
if (i.valid) {do something interesting } else { undefined }

Puoi vedere quanto sopra ha lo stesso potenziale per accedere a i.value quando non è valido. Quindi è necessario definire un'API per fare qualsiasi lavoro con questi dati per evitare che ciò accada. Ad esempio:

myType add_numbers(myType first, myType second)
{
myType returnValue = {0,0};
if (first.valid && second.valid) 
  { returnValue.valid = TRUE;
    returnValue.value = first.value + second.value;
    // TODO deal with overflow here
  }
  return (returnValue);
}

Puoi evitare l'overhead della creazione di un'API complessa. C'è un'altra soluzione con funziona bene con la lingua e impedirà qualsiasi riferimento di dati non intenzionale. Ti permette di avere veramente il concetto di un valore indefinito nel tuo codice. Questa soluzione è usare i puntatori. Un puntatore punterà a un elemento di dati valido o sarà indefinito, che è esattamente quello che stai cercando. Ovviamente, il rischio è che il tuo codice si arresti in modo anomalo se tenta di accedere a un valore indefinito a meno che tu non controlli sempre (quindi non puoi allontanarti da quello).

unsigned char * theData = NULL;
...
// data gets a value
unsigned char actualData = something;
theData = &actualData;
...
if (!theData) { do something interesting } else { undefined }

Suggerisco che la scelta dovrebbe essere regolata dalla tolleranza per l'errore e da come è possibile proteggersi dagli errori. L'approccio API è il più sicuro se ci si aspetta che altri utilizzino questo codice. Finché l'API viene seguita, i bug vengono ridotti al minimo. Gli altri due approcci sono aperti ai bug che determinano il calcolo del valore errato o il blocco del programma.

    
risposta data 22.10.2016 - 00:09
fonte

Leggi altre domande sui tag