Was sind einige Fehler, die Sie in C-APIs verrückt machen (einschließlich Standardbibliotheken, Bibliotheken von Drittanbietern und Header innerhalb eines Projekts)? Ziel ist es, API-Design-Fallstricke in C zu identifizieren, damit Benutzer, die neue C-Bibliotheken schreiben, aus Fehlern der Vergangenheit lernen können.
Erklären Sie, warum der Fehler schlecht ist (vorzugsweise anhand eines Beispiels), und schlagen Sie eine Verbesserung vor. Obwohl Ihre Lösung im wirklichen Leben möglicherweise nicht praktikabel ist (es ist zu spät, um strncpy
sie zu beheben ), sollte sie künftigen Bibliotheksautoren den Kopf zerbrechen.
Obwohl der Schwerpunkt dieser Frage auf C-APIs liegt, sind Probleme, die sich auf Ihre Fähigkeit auswirken, sie in anderen Sprachen zu verwenden, willkommen.
Bitte geben Sie einen Fehler pro Antwort an, damit die Demokratie die Antworten sortieren kann.
quelle
malloc
Zeichenfolge es beheben würde. Ich denke, ein gutes Beispiel mit der ersten Antwort könnte dieser Frage wirklich helfen, Erfolg zu haben. Vielen Dank!Antworten:
Funktionen mit inkonsistenten oder unlogischen Rückgabewerten. Zwei gute Beispiele:
1) Einige Windows-Funktionen, die einen HANDLE zurückgeben, verwenden NULL / 0 für einen Fehler (CreateThread), andere verwenden INVALID_HANDLE_VALUE / -1 für einen Fehler (CreateFile).
2) Die POSIX-Funktion 'time' gibt bei einem Fehler '(time_t) -1' zurück, was wirklich unlogisch ist, da 'time_t' entweder ein vorzeichenbehafteter oder ein vorzeichenloser Typ sein kann.
quelle
int time(time_t *out);
undBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Funktionen oder Parameter mit nicht beschreibenden oder positiv verwirrenden Namen. Zum Beispiel:
1) CreateFile erstellt in der Windows-API keine Datei, sondern ein Dateihandle. Es kann eine Datei erstellen, genau wie 'open', wenn es über einen Parameter dazu aufgefordert wird. Dieser Parameter hat Werte namens 'CREATE_ALWAYS' und 'CREATE_NEW', deren Namen nicht einmal auf ihre Semantik hinweisen. (Bedeutet 'CREATE_ALWAYS', dass es fehlschlägt, wenn die Datei vorhanden ist? Oder erstellt es eine neue Datei darüber? Bedeutet 'CREATE_NEW', dass es immer eine neue Datei erstellt und fehlschlägt, wenn die Datei bereits vorhanden ist? Oder erstellt es eine neue Datei darüber?)
2) pthread_cond_wait in der POSIX pthreads-API, die trotz ihres Namens eine bedingungslose Wartezeit darstellt.
quelle
pthread_cond_wait
bedeutet nicht , „bedingt warten“. Es bezieht sich auf die Tatsache, dass Sie auf eine Bedingungsvariable warten .Undurchsichtige Typen, die als vom Typ gelöschte Handles über die Benutzeroberfläche übergeben werden. Das Problem ist natürlich, dass der Compiler den Benutzercode nicht auf korrekte Argumenttypen überprüfen kann.
Dies gibt es in verschiedenen Formen und Geschmacksrichtungen, einschließlich, aber nicht beschränkt auf:
void*
MissbrauchVerwendung
int
als Ressourcenhandle (Beispiel: CDI-Bibliothek)streng typisierte Argumente
Je mehr unterschiedliche Typen (= können nicht vollständig austauschbar verwendet werden) demselben gelöschten Typ zugeordnet werden, desto schlechter. Das Mittel besteht natürlich einfach darin, typsichere undurchsichtige Zeiger nach dem Vorbild von (Beispiel C) bereitzustellen:
quelle
Funktionen mit inkonsistenten und oft umständlichen Konventionen für die Rückgabe von Zeichenfolgen.
Beispielsweise fragt getcwd nach einem vom Benutzer bereitgestellten Puffer und seiner Größe. Dies bedeutet, dass eine Anwendung entweder eine beliebige Grenze für die aktuelle Verzeichnislänge festlegen muss oder Folgendes tun muss ( von CCAN ):
Meine Lösung: Geben Sie einen
malloc
ed-String zurück. Es ist einfach, robust und nicht weniger effizient. Mit Ausnahme von eingebetteten Plattformen und älteren Systemenmalloc
ist das eigentlich recht schnell.quelle
snprintf(buf, 32, "%d", n)
, bei denen die Ausgabelänge vorhersehbar ist (sicherlich nicht mehr als 30, esint
sei denn, Ihr System ist wirklich sehr groß). In der Tat ist malloc auf vielen Systemen nicht verfügbar, aber für Desktop- und Serverumgebungen ist es das und es funktioniert wirklich gut.Funktionen, die zusammengesetzte Datentypen nach Wert annehmen / zurückgeben oder Rückrufe verwenden.
Noch schlimmer, wenn dieser Typ eine Vereinigung ist oder Bitfelder enthält.
Aus der Sicht eines C-Aufrufers sind diese eigentlich in Ordnung, aber ich schreibe nicht in C oder C ++, es sei denn, dies ist erforderlich, daher rufe ich normalerweise über ein FFI an. Die meisten FFIs unterstützen keine Gewerkschaften oder Bitfelder, und einige (wie Haskell und MLton) können keine durch Wert übergebenen Strukturen unterstützen. Für diejenigen, die By-Value-Strukturen verarbeiten können, werden zumindest Common Lisp und LuaJIT auf langsame Pfade gezwungen. Die Common Foreign Function Interface von Lisp muss einen langsamen Aufruf über libffi ausführen, und LuaJIT weigert sich, den Codepfad, der den Aufruf enthält, JIT-kompiliert zu haben. Funktionen, die möglicherweise auf die Hosts zurückrufen, lösen auch langsame Pfade auf LuaJIT, Java und Haskell aus, wobei LuaJIT einen solchen Aufruf nicht kompilieren kann.
quelle