2012-05-10 13 views
19

Viele Male habe ich eine Funktion, eine variable Anzahl von Argumenten erhalten möchten, indem NULL beendet, zum BeispielTypesafe varargs in C mit gcc

#define push(stack_t stack, ...) _push(__VARARG__, NULL); 
func _push(stack_t stack, char *s, ...) { 
    va_list args; 
    va_start(args, s); 
    while (s = va_arg(args, char*)) push_single(stack, s); 
} 

Kann ich gcc oder Klappern anweisen zu warnen, wenn foo nicht char* Variablen empfängt ? Ähnliches wie __attribute__(format), aber für mehrere Argumente desselben Zeigertyps.

+2

Wenn alle Typen vom selben Typ sein müssen, haben Sie nur ein Array von ihnen übergeben? –

+0

Kein natives Array in C89. Sie können zB "f ({1,2,3,0})" mit MS C Compiler nicht übergeben. – mikebloch

+3

willst du etwas, das mit gcc oder MS C funktioniert? Bitte passend etikettieren. Mit C99 gibt es Lösungen, die typsicher sind. –

Antwort

15

Ich weiß, du denkst __attribute__((sentinel)) die Verwendung irgendwie, aber das ist eine falsche Fährte.

Was Sie wollen, ist so etwas wie dies zu tun:

#define push(s, args...) ({     \ 
    char *_args[] = {args};      \ 
    _push(s,_args,sizeof(_args)/sizeof(char*)); \ 
}) 

die Wraps:

void _push(stack_t s, char *args[], int argn); 

, die Sie genau die Art und Weise schreiben Sie können Sie es schreiben würde hoffen kann!

Dann können Sie anrufen:

push(stack, "foo", "bar", "baz"); 
push(stack, "quux"); 
+0

Wird es nicht unnötiges Kopieren der 'char *' Zeiger verursachen? –

+0

@ Chi-Lan - Abhängig von der Implementierung von '_push()' GCC entfernt unnötige Lasten. – geocar

+0

Ich versuche etwas ähnliches zu tun. Ich möchte wirklich ein Attribut, das die Typsicherheit der Varargs garantiert. Grundsätzlich möchte ich sicherstellen, dass sie alle char * sind. Weiß jemand, wie man das macht? –

1

kann ich denke nur an so etwas wie diese:

#include <stddef.h> 
#include <stdio.h> 
#include <stdlib.h> 

typedef struct tArg 
{ 
    const char* Str; 
    struct tArg* Next; 
} tArg; 

tArg* Arg(const char* str, tArg* nextArg) 
{ 
    tArg* p = malloc(sizeof(tArg)); 
    if (p != NULL) 
    { 
    p->Str = str; 
    p->Next = nextArg; 
    } 
    else 
    { 
    while (nextArg != NULL) 
    { 
     p = nextArg->Next; 
     free(nextArg); 
     nextArg = p; 
    } 
    } 
    return p; 
} 

void PrintR(tArg* arg) 
{ 
    while (arg != NULL) 
    { 
    tArg* p; 
    printf("%s", arg->Str); 
    p = arg->Next; 
    free(arg); 
    arg = p; 
    } 
} 

void (*(*(*(*(*(*(*Print8 
    (const char* Str)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*) 
{ 
    printf("%s", Str); 
    // There's probably a UB here: 
    return (void(*(*(*(*(*(*(*) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*))&Print8; 
} 

int main(void) 
{ 
    PrintR(Arg("HELLO", Arg(" ", Arg("WORLD", Arg("!", Arg("\n", NULL)))))); 
// PrintR(Arg(1, NULL));  // warning/error 
// PrintR(Arg(&main, NULL)); // warning/error 
// PrintR(Arg(0, NULL));  // no warning/error 
// PrintR(Arg((void*)1, NULL)); // no warning/error 

    Print8("hello")(" ")("world")("!")("\n"); 
// Same warning/error compilation behavior as with PrintR() 
    return 0; 
} 
+3

Oh Bruder! Ich bin mir nicht sicher, ob ich lachen oder weinen soll ...;-) –

+0

@ Chi-Lan: C ist besonders ausdrucksstark. :) –

+0

'Print8' ist das beste und schlechteste Bit von C, das ich je gesehen habe. – huon

-1

Das Problem mit C variadics ist, dass sie wirklich danach, nicht wirklich in die Sprache ausgelegt sind verschraubt. Das Hauptproblem ist, dass die variadischen Parameter anonym sind, sie haben keine Handles, keine Identifikatoren. Dies führt dazu, dass die unhandlichen VA-Makros Verweise auf Parameter ohne Namen generieren. Es führt auch dazu, dass man den Makros sagen muss, wo die variadische Liste beginnt und welchen Typ die Parameter haben sollen.

Alle diese Informationen sollten in der Sprache selbst in der richtigen Syntax codiert werden.

Zum Beispiel könnte eine vorhandene C-Syntax mit formalen Parametern nach dem Auslassungs verlängern, wie so

void foo (... int counter, float arglist); 

Vereinbarungs der erste Parameter für das Argument Zahl sein könnte und die zweite für die Argumentliste. Innerhalb des Funktionskörpers könnte die Liste syntaktisch als Array behandelt werden.

Mit einer solchen Konvention wären die variadischen Parameter nicht mehr anonym. Innerhalb des Funktionskörper kann der Zähler wie jeder andere Parameter referenziert werden und die Listenelemente können referenziert werden, als ob sie Array-Elemente eines Arrays Parameter waren, wie so

void foo (... int counter, float arglist) { 
    unsigned i; 
    for (i=0; i<counter; i++) { 
    printf("list[%i] = %f\n", i, arglist[i]); 
    } 
} 

Mit einem solchen Merkmal in die Sprache selbst gebaut Jeder Verweis auf arglist[i] würde dann in die entsprechenden Adressen auf dem Stapelrahmen übersetzt werden. Es wäre nicht notwendig, dies über Makros zu tun.

Darüber hinaus würde die Anzahl der Argumente automatisch vom Compiler eingefügt werden, wodurch die Möglichkeit für Fehler weiter reduziert wird.

Ein Aufruf

foo(1.23, 4.56, 7.89); 

würde kompiliert werden, als ob es

foo(3, 1.23, 4.56, 7.89); 

Innerhalb der Funktion Körper geschrieben worden war, keinen Zugang zu einem Element über die tatsächliche Anzahl der Argumente sein könnte tatsächlich weitergegeben überprüft zur Laufzeit und verursacht einen Kompilierungsfehler, wodurch die Sicherheit stark erhöht wird.

Zu guter Letzt werden alle Variadic-Parameter eingegeben und können zur Kompilierzeit wie bei nicht-variadischen Parametern überprüft werden.

In einigen Anwendungsfällen wäre es natürlich wünschenswert, alternierende Typen zu verwenden, beispielsweise beim Schreiben einer Funktion zum Speichern von Schlüsseln und Werten in einer Sammlung. Dies könnte auch einfach untergebracht werden, indem mehr formalen Parameter nach dem Auslassungszeichen ermöglicht, wie so

void store (collection dict, ... int counter, key_t key, val_t value); 

könnte diese Funktion dann als

store(dict, key1, val1, key2, val2, key3, val3); 

aber als kompilierte würde genannt werden, wenn es

geschrieben worden war

Die Arten der tatsächlichen Parameter würden Kompilierzeit gegen die entsprechenden variadischen formalen Parameter überprüft werden.

Im Körper der Funktion würde der Zähler wieder durch ihre Kennung, Schlüssel und Werte Bezug genommen werden würde referenziert werden, als ob sie Arrays waren,

key[i] auf den Schlüssel des i-ten bezieht Schlüssel/Wert-Paar value[i] bezieht sich auf den Wert des i-ten Wertepaars

und diese Referenzen würden zu ihren jeweiligen Adressen auf dem Stapelrahmen kompiliert werden.

Nichts davon ist wirklich schwer zu tun, noch war es jemals. Die Konstruktionsphilosophie von C ist diesen Funktionen jedoch nicht zuträglich.

Ohne wagen C-Compiler-Implementierer (oder C-Präprozessor Implementierer) die Führung übernehmen, dies zu implementieren oder eine ähnliche Regelung ist es unwahrscheinlich, dass wir jemals etwas von dieser Art in C. siehe

Das Problem ist, dass Leute, die sind an Typensicherheit interessiert und sind bereit, in die Arbeit zu investieren, um ihre eigenen Compiler zu bauen, um zu dem Schluss zu kommen, dass die C-Sprache nicht mehr zu retten ist und man gleich zu Beginn mit einer besser gestalteten Sprache beginnen kann.

Ich war selbst dort, beschloss schließlich, den Versuch aufzugeben, dann implementieren wir eine von Wirths Sprachen und fügte stattdessen typsichere Variablen hinzu. Ich bin seitdem auf andere Leute gestoßen, die mir von ihren eigenen abgebrochenen Versuchen erzählt haben. Richtige Art sichere variadics in C scheinen balanciert, um schwer zu bleiben.

+0

Der Grund, warum nicht mit der Syntax zu tun hat, aber die Änderungen an der ABI: Die x86_64 ABI und i386 fastcall ABIs zum Beispiel erfordern keine Argumente haben eine "Adresse", und während Stapel-Engines auf modernen CPUs wirklich tun Sie viel für die Kosten der Verschütten Argumente, die doppelte Verschwendung der wertvollen L1 macht es zu einem Nettoverlust. Wenn Sie nur mit der Syntax und der Ausdruckskraft spielen wollten, könnten Sie C4 ausprobieren, da es sowieso einen eigenen (virtuellen) ABI hat: https://github.com/rswier/c4 – geocar

+0

Wenn diese Syntax in die Sprache eingepasst wurde, dann Die Ausführer müssten sie ihren jeweiligen ABIs zuordnen. Es gibt keinen Grund, warum Sie das nicht tun könnten. Die Referenzen werden zur Kompilierzeit aufgelöst (zumindest in Bezug auf verschiebbare Referenzen) und der Compiler kennt das Ziel-ABI. Darüber hinaus gibt es keinen Grund, warum eine variadische Argumentliste nicht auf einen Registersatz abgebildet werden könnte. Der Implementierungsaufwand ist ungefähr derselbe wie bei der Implementierung der VA-Makros. Das eigentliche Problem ist, dass die variadischen Parameter anonym sind. Das steht im Weg, es typsicher zu machen. – trijezdci

+0

C wurde um einen ABI herum entworfen, nicht umgekehrt. Wenn Sie ein Pascal haben möchten, das wie C aussieht, können Sie eines von c4 erstellen, wie ich es vorschlage - es ist architektonisch ähnlich wie viele Pascal-Implementierungen. – geocar