10 Stimmen

Ist es eine gute Idee, varargs in einer C-API zu verwenden, um Schlüssel-Wert-Paare zu setzen?

Ich schreibe eine API, die eine Vielzahl von verschiedenen Feldern in einer Struktur aktualisiert.

Ich könnte das Hinzufügen künftiger Felder erleichtern, indem ich die Aktualisierungsfunktion variabel gestalte:

update(FIELD_NAME1, 10, FIELD_NAME2, 20);

dann später hinzufügen FIELD_NAME3 ohne die bestehenden Anrufe zu ändern:

update(FIELD_NAME1, 10, FIELD_NAME2, 20, FIELD_NAME3, 30);

Worte der Weisheit, bitte?

11voto

Michael Punkte 52790

Im Allgemeinen nicht.

Varargs hebt eine Menge Typsicherheit auf - man kann Zeiger, Floats usw. anstelle von Ints übergeben und es wird ohne Probleme kompiliert. Der Missbrauch von varargs, wie das Weglassen von Argumenten, kann zu seltsamen Abstürzen aufgrund von Stackkorruption oder dem Lesen ungültiger Zeiger führen.

Der folgende Aufruf wird zum Beispiel kompiliert und führt zu Abstürzen oder anderem seltsamen Verhalten:

UpdateField(6, "Field1", 7, "Field2", "Foo");

Die ersten 6 sind, wie viele Parameter zu erwarten sind. Es wird den String-Zeiger "Foo" in einen int umwandeln, um ihn in Feld2 einzutragen, und es wird versuchen, zwei andere Parameter zu lesen und zu interpretieren, die nicht vorhanden sind, was hier wahrscheinlich zu einem Absturz führen wird, weil dereferencing stack noise.

Ich glaube, dass die Implementierung von varargs in C ein Fehler ist (in der heutigen Umgebung - 1972 war es wahrscheinlich absolut sinnvoll). Die Implementierung besteht darin, dass man eine Reihe von Werten auf dem Stack übergibt und dann der Aufrufer den Stack abläuft, um Parameter zu sammeln, die auf seiner Interpretation eines anfänglichen Kontrollparameters basieren. Diese Art der Implementierung schreit förmlich danach, dass Sie einen Fehler machen, was sehr schwer zu diagnostizieren sein könnte. Die C#-Implementierung dieser Methode, bei der ein Array von Objekten mit einem Attribut an die Methode übergeben wird, ist einfach vernünftiger, wenn auch nicht direkt auf die C-Sprache übertragbar.

7voto

paxdiablo Punkte 809679

Ich neige dazu, varargs zu vermeiden, außer in einem bestimmten Fall, wo es sehr nützlich ist. Variable Argumente bieten nicht wirklich einen großen Nutzen, der über das hinausgeht, was durch einzelne Funktionsaufrufe erreicht werden kann, insbesondere in Ihrem Fall.

In Bezug auf die Lesbarkeit (und das ist in der Regel das, was ich über rohe Geschwindigkeit außer in sehr speziellen Fällen bevorzuge), gibt es keinen wirklichen Unterschied zwischen den folgenden zwei Optionen (ich habe eine Zählung zu den varargs Versionen hinzugefügt, da Sie entweder eine Zählung oder Sentinel benötigen, um das Ende der Daten zu erkennen):

update(2, FIELD_NAME1, 10, FIELD_NAME2, 20);
update(3, FIELD_NAME3, 10, FIELD_NAME4, 20, FIELD_NAME5, 30);
/* ========== */
update(FIELD_NAME1, 10);
update(FIELD_NAME2, 20);
update(FIELD_NAME3, 10);
update(FIELD_NAME4, 20);
update(FIELD_NAME5, 30);

Da die varargs-Version länger wird, müssen Sie sie aus Gründen der Formatierung ohnehin aufteilen:

update(5,
    FIELD_NAME1, 10,
    FIELD_NAME2, 20,
    FIELD_NAME3, 10,
    FIELD_NAME4, 20,
    FIELD_NAME5, 30);

Die Methode "ein Aufruf pro Feldname" führt zu einem einfacheren Code in der Funktion selbst und beeinträchtigt nicht die Lesbarkeit der Aufrufe. Außerdem kann der Compiler auf diese Weise bestimmte Fehler erkennen, was bei varargs nicht möglich ist, z. B. falsche Typen oder eine Abweichung zwischen der vom Benutzer angegebenen und der tatsächlichen Anzahl.

Wenn Sie wirklich muss eine einzige Funktion aufrufen zu können, würde ich mich dafür entscheiden:

void update (char *k1, int v1) {
    ...
}
void update2 (char *k1, int v1, char *k2, int v2) {
    update (k1, v1);
    update (k2, v2);
}
void update3 (char *k1, int v1, char *k2, int v2, char *k3, int v3) {
    update (k1, v1); /* or these two could be a single */
    update (k2, v2); /*   update2 (k1, v1, k2, v2);    */
    update (k3, v3);
}
/* and so on. */

Sie könnten die höheren Funktionen sogar als Makros ausführen, ohne die Typsicherheit zu verlieren.

Der einzige Ort, an dem ich dazu neige, varargs-Funktionen zu verwenden, ist, wenn ich die gleiche Funktionalität wie printf() - Ich musste zum Beispiel gelegentlich Protokollierungsbibliotheken mit Funktionen wie logPrintf() die die gleiche Funktionalität bieten. Ich kann mir nicht vorstellen, dass jede In meiner langen (und ich meine wirklich langen :-) Zeit an der Front habe ich es noch kein einziges Mal benutzen müssen.

Nebenbei bemerkt, wenn Sie sich für die Verwendung von varargs entscheiden, neige ich dazu, eher sentinels als counts zu verwenden, da dies Fehlanpassungen beim Hinzufügen von Feldern verhindert. Man könnte leicht vergessen, die Zählung anzupassen, und am Ende steht man da:

update (2, k1, v1, k2, v2, k3, v3);

beim Addieren, was heimtückisch ist, weil es k3/v3 stillschweigend überspringt, oder:

update (3, k1, v1, k2, v2);

beim Löschen, was mit ziemlicher Sicherheit fatal für die erfolgreiche Ausführung Ihres Programms ist.

Ein Wächter verhindert dies (natürlich nur, wenn man den Wächter nicht vergisst):

update (k1, v1, k2, v2, k3, v3, NULL);

5voto

Chris Lutz Punkte 69879

Ein Problem mit varargs in C ist, dass man nicht weiß, wie viele Argumente übergeben werden, so dass man dies als weiteren Parameter benötigt:

update(2, FIELD_NAME1, 10, FIELD_NAME2, 20);

update(3, FIELD_NAME1, 10, FIELD_NAME2, 20, FIELD_NAME3, 30);

3voto

ryansstack Punkte 1306

Warum nicht ein einziges Argument, ein Array. Noch besser, ein Zeiger auf ein Array.

struct field {
  int val;
  char* name;
};

oder sogar...

union datatype {
  int a;
  char b;
  double c;
  float f;
// etc;
}; 

dann

struct field {
  datatype val;
  char* name;
};

union (struct* field_name_val_pairs, int len);

ok 2 Args. Ich habe gelogen und dachte, ein Längenparam wäre gut.

2voto

Paul Sonier Punkte 37609

Ich würde jede "Aktualisierungsfunktion", die extern (oder sogar intern) verwendet werden soll und dieselbe Funktion zur Aktualisierung vieler verschiedener Felder in einer Struktur verwendet, genau unter die Lupe nehmen. Gibt es einen bestimmten Grund, warum Sie keine getrennten Funktionen für die Aktualisierung der Felder haben können?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X