2016-04-01 13 views
35

C'è un modo per rendere C un po 'più consapevole dei tipi e assicurare la sicurezza del tipo?
Considerate questo:Tipo di sicurezza in C

typedef unsigned cent_t; 
typedef unsigned dollar_t; 

#define DOLLAR_2_CENT(dollar)  ((cent_t)(100*(dollar))) 

void calc(cent_t amount) { 
    // expecting 'amount' to semantically represents cents... 
} 

int main(int argc, char* argv[]) { 
    dollar_t amount = 50; 
    calc(DOLLAR_2_CENT(amount)); // ok 
    calc(amount);     // raise warning 
    return 0; 
} 

C'è un modo per rendere il codice di cui sopra al-almeno alzare avvertimento dal gcc?
So che posso usare C-structs per avvolgere unsigned s e ottenere il risultato desiderato, mi stavo chiedendo se c'era un modo più elegante per farlo.
Può essere un po 'di più?

+17

Fai '' cent_t' e dollar_t' una struttura, con un solo membro? – Leandros

+11

C non è davvero un linguaggio sicuro per i caratteri. Il modo più semplice per * emulare * sicurezza del tipo è usare le strutture. –

+0

Dichiarare tutte le funzioni correttamente prima dell'uso (elenco completo degli argomenti delle funzioni, ecc.). Passa le strutture piuttosto che i tipi di base (o typedef dei tipi di base). Preferisci funzioni in linea su macro. Aumenta i livelli di avviso sul tuo compilatore. – Peter

risposta

8

Per ottenere questo risultato è necessario utilizzare uno strumento di analisi statico nel processo di creazione.

Ad esempio, se si esegue PCLint sul vostro codice, dà questa uscita:

[Warning 632] Assignment to strong type 'cent_t' in context: arg. no. 1 
    [Warning 633] Assignment from a strong type 'dollar_t' in context: arg. no. 1 

http://www.gimpel.com/html/strong.htm

+0

Buono! Lo proverò. Grazie. –

+0

Esiste un'alternativa gratuita? :( –

+0

Sto accettando questa risposta, perché dopo ulteriori ricerche, sembra che questo sia esattamente quello che stavo cercando: uno strumento di analisi statica che mi avviserà/rileverà un errore ogni volta che creo errori di tipi: –

10

L'aliasing ha un significato stretto molto specifico in C, e non è quello che hai in mente. Puoi dire "typedefing".

E la risposta è no, non è possibile. Non in modo elegante in ogni caso. È possibile utilizzare una struttura per ciascun tipo numerico e un insieme separato di funzioni per eseguire operazioni aritmetiche con ciascuna di esse. Tranne quando si parla di moltiplicazione, sei sfortunato. Per moltiplicare i piedi per libbra, hai bisogno di un terzo tipo. Hai anche bisogno di tipi per piedi quadrati, piedi cubi, secondi per la potenza di meno due e un numero infinito di altri tipi.

Se questo è quello che cerchi, C non è la lingua giusta.

30

Il problema è che C non considera i due typedef come tipi distintivi, poiché entrambi sono del tipo unsigned.

Ci sono vari trucchi per evitare questo. Una cosa sarebbe cambiare i tuoi tipi in enumerati. I buoni compilatori applicheranno gli avvertimenti di digitazione più forti sulle conversioni implicite a/da un determinato tipo di enumerazione a qualsiasi altro tipo.

Anche se non si dispone di un buon compilatore, con enumerazioni si potrebbe fare questo:

typedef enum { FOO_CENT } cent_t; 
typedef enum { FOO_DOLLAR} dollar_t; 

#define DOLLAR_2_CENT(dollar)  ((cent_t)(100*(dollar))) 

void calc(cent_t amount) { 
    // expecting 'amount' to semantically represents cents... 
} 

#define type_safe_calc(amount) _Generic(amount, cent_t: calc(amount)) 

int main(int argc, char* argv[]) { 
    dollar_t amount = 50; 
    type_safe_calc(DOLLAR_2_CENT(amount)); // ok 
    type_safe_calc(amount);   // raise warning 

    return 0; 
} 

Un trucco più convenzionale/tradizionale è quello di utilizzare un wrapper struct generica, in cui si utilizza un "ticket" enum per contrassegnare il tipo. Esempio:

typedef struct 
{ 
    type_t type; 
    void* data; 
} wrapper_t; 

... 

cent_t my_2_cents; 
wrapper_t wrapper = {CENT_T, &my_2_cents}; 

... 

switch(wrapper.type) 
{ 
    case CENT_T: calc(wrapper.data) 
    ... 
} 

Il vantaggio è che funziona con qualsiasi versione C. Lo svantaggio è rappresentato dal sovraccarico di codice e memoria e consente solo controlli di runtime.

+0

I valori di 'FOO_CENT' e' FOO_DOLLAR' sono solo fittizi? –

+0

Le strutture sono fuori questione (come hai detto tu, ci sono penalità di codice e memoria, e non posso permettermelo ...) –

+0

@ so.very.tired Sì, è solo un maniaco senza senso. È possibile utilizzare un enum per memorizzare qualsiasi valore intero, sebbene la grandezza dell'enumerazione effettivamente dipenda dal compilatore. – Lundin

5

EDIT: Ecco un'alternativa che funziona anche in C89, nel caso in cui il compilatore non lo fa supporto selettore _Generic (un sacco di compilatori non lo fanno e spesso sei bloccato con ciò che è installato sul tuo computer).

È possibile utilizzare macro per semplificare l'uso dei wrapper struct.

#define NEWTYPE(nty,oty) typedef struct { oty v; } nty 
#define FROM_NT(ntv)  ((ntv).v) 
#define TO_NT(nty,val)  ((nty){(val)}) /* or better ((nty){ .v=(val)}) if C99 */ 


NEWTYPE(cent_t, unsigned); 
NEWTYPE(dollar_t, unsigned); 

#define DOLLAR_2_CENT(dollar)  (TO_NT(cent_t, 100*FROM_NT(dollar))) 

void calc(cent_t amount) { 
    // expecting 'amount' to semantically represents cents... 
} 

int main(int argc, char* argv[]) { 
    dollar_t amount = TO_NT(dollar_t, 50); // or alternatively {50}; 
    calc(DOLLAR_2_CENT(amount)); // ok 
    calc(amount);     // raise warning 
    return 0; 
} 

È ancora più forte di un avviso. Ecco il risultato della compilazione con gcc 5.1

 
$ gcc -O3 -Wall Edit1.c 
Edit1.c: In function ‘main’: 
Edit1.c:17:10: error: incompatible type for argument 1 of ‘calc’ 
    calc(amount);     // raise warning 
     ^
Edit1.c:10:6: note: expected ‘cent_t {aka struct }’ but argument is of type ‘dollar_t {aka struct }’ 
void calc(cent_t amount);// { 

e qui il risultato con gcc 3,4

 
$ gcc -O3 -Wall Edit1.c 
Edit1.c: In function 'main': 
Edit1.c:17: error: incompatible type for argument 1 of 'calc' 
+0

C89 non ha inizializzatori designati, la terza riga dell'esempio non verrà compilata – Leandros

+0

Hai provato questo con '-std = c89' – cat

+0

Sì. È stato compilato senza preavviso Può essere che dovrei provare '-pedantic'. –