2016-04-01 13 views
35

Gibt es eine Möglichkeit, C etwas mehr auf Typen aufmerksam zu machen und Typ-Sicherheit zu gewährleisten?
Bedenken Sie:Typ-Sicherheit 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; 
} 

Gibt es einen Weg, um den obigen Code zu machen at-dest Warnung durch den gcc zu erhöhen?
Ich weiß, ich kann C-Strukturen verwenden, um unsigned s zu wickeln und das gewünschte Ergebnis zu erzielen, ich habe mich nur gefragt, ob es eine elegantere Möglichkeit war, es zu tun.
Kann es ein bisschen mehr sein?

+17

Make 'cent_t' und' dollar_t' eine Struktur mit nur einem Mitglied? – Leandros

+11

C ist nicht wirklich eine typsichere Sprache. Der einfachste Weg zur * Emulation * Typ-Sicherheit ist die Verwendung von Strukturen. –

+0

Deklarieren Sie alle Ihre Funktionen vor der Verwendung (vollständige Liste der Funktionsargumente usw.). Übergeben Sie Strukturen und nicht grundlegende Typen (oder Typdefinitionen von Basistypen). Bevorzugen Sie Inline-Funktionen über Makros. Erhöhen Sie die Warnstufen Ihres Compilers. – Peter

Antwort

8

Sie müssen ein statisches Analysewerkzeug in Ihrem Build-Prozess verwenden, um dies zu erreichen.

[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

Gut! Ich werde es versuchen. Vielen Dank. –

+0

Gibt es eine kostenlose Alternative? :( –

+0

Ich akzeptiere diese Antwort, denn nach weiteren Recherchen scheint es genau das zu sein, wonach ich gesucht habe: Ein statisches Analysewerkzeug, das mich warnen/Fehler melden, wenn ich Typenfehler mache. –

10

Aliasing hat eine sehr spezifische, enge Bedeutung in C, und es ist nicht, was Sie im Sinn haben. Vielleicht möchten Sie "typedefing" sagen.

Und die Antwort ist nein, können Sie nicht. Nicht auf elegante Art und Weise. Sie können eine Struktur für jeden numerischen Typ und einen separaten Satz von Funktionen verwenden, um mit jeder Arithmetik zu arbeiten. Außer wenn es um Multiplikation geht, hast du kein Glück. Um Füße für Pfund zu multiplizieren, brauchen Sie einen dritten Typ. Sie brauchen auch Typen für Füße im Quadrat, Füße gewürfelt, Sekunden auf die Kraft von minus zwei und eine unendliche Anzahl von anderen Arten.

Wenn Sie danach suchen, ist C nicht die richtige Sprache.

30

Das Problem ist, dass C Ihre zwei typedefs nicht als unterscheidbare Typen behandelt, da beide vom Typ unsigned sind.

Es gibt verschiedene Tricks, um dem auszuweichen. Eine Sache wäre, deine Typen zu Enums zu ändern. Gute Compiler erzwingen stärkere Typisierungswarnungen bei impliziten Konvertierungen von/zu einem bestimmten Aufzählungstyp in einen anderen Typ.

Auch wenn Sie nicht über einen guten Compiler, mit Aufzählungen können Sie dies tun:

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; 
} 

Eine konventionellere/traditionell Trick ist, eine generische Struktur Wrapper zu verwenden, in dem Sie eine verwenden "ticket" enum um den Typ zu markieren. Beispiel:

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) 
    ... 
} 

Der Vorteil ist, dass es mit jeder C-Version funktioniert. Nachteil ist der Overhead von Code und Arbeitsspeicher, der nur Laufzeitprüfungen zulässt.

+0

Sind die 'FOO_CENT' und' FOO_DOLLAR' nur Dummy-Werte? –

+0

Structs kommen nicht in Frage (wie du gesagt hast, es gibt Speicher- und Code-Strafen, und das kann ich mir nicht leisten ...) –

+0

@ so.very.tired Ja, es sind nur Nonsense-Dummies. Sie können eine Enumeration verwenden, um beliebige Integer-Werte zu speichern. Wie groß die Enumeration tatsächlich ist, hängt jedoch vom Compiler ab. – Lundin

5

EDIT: Hier eine Alternative, die funktioniert auch in C89, tut falls Ihr Compiler nicht

Zum Beispiel, wenn Sie PCLint auf Ihrem Code ausführen, es gibt diese Ausgabe Unterstützung _Generic Selectors (viele Compiler nicht und oft Sie stecken fest, was auf Ihrem Computer installiert ist).

Sie können Makros verwenden, um die Verwendung von struct Wrapper zu vereinfachen.

#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; 
} 

Sie werden noch stärker als eine Warnung. Hier ist das Kompilierergebnis mit 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);// { 

und hier das Ergebnis mit 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 hat keine Initialisierer, die dritte Zeile Ihres Beispiels würde nicht kompiliert – Leandros

+0

Haben Sie das mit '-std = c89' versucht – cat

+0

Ja, es kompiliert ohne Warnung Ich hätte "-pedantic" ausprobieren sollen. –