Ich arbeite derzeit an einer Bibliothek, die in C geschrieben ist. Viele Funktionen dieser Bibliothek erwarten einen String als char*
oder const char*
in ihren Argumenten. Ich habe mit diesen Funktionen begonnen und immer die Länge des Strings als erwartet, size_t
so dass keine Null-Terminierung erforderlich war. Beim Schreiben von Tests wurde jedoch häufig Folgendes verwendet strlen()
:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Das Vertrauen des Benutzers, ordnungsgemäß terminierte Zeichenfolgen zu übergeben, würde zu weniger sicherem, aber präziserem und (meiner Meinung nach) lesbarem Code führen:
libFunction("I hope there's a null-terminator there!");
Also, was ist die vernünftige Praxis hier? Machen Sie die Verwendung der API komplizierter, aber zwingen Sie den Benutzer, über ihre Eingaben nachzudenken, oder dokumentieren Sie die Anforderung einer nullterminierten Zeichenfolge und vertrauen Sie dem Aufrufer?
CreateFile
einenLPTCSTR lpFileName
Parameter als Eingabe. Vom Aufrufer wird keine Länge der Zeichenfolge erwartet. Tatsächlich ist die Verwendung von mit NUL abgeschlossenen Zeichenfolgen so tief verwurzelt, dass in der Dokumentation nicht einmal erwähnt wird, dass der Dateiname mit NUL abgeschlossen sein muss (aber natürlich muss dies der Fall sein).LPSTR
Typ sagt , dass Strings kann NUL-terminiert sein, und wenn nicht , wird diese in der zugehörigen Beschreibung angegeben werden. Sofern nicht ausdrücklich anders angegeben, wird erwartet, dass solche Zeichenfolgen in Win32 NUL-terminiert sind.StringCbCat
zum Beispiel hat nur das Ziel einen maximalen Puffer, was Sinn macht. Die Quelle ist immer noch eine gewöhnliche NUL-terminierte C-Zeichenfolge. Vielleicht können Sie Ihre Antwort verbessern, indem Sie den Unterschied zwischen einem Eingabeparameter und einem Ausgabeparameter verdeutlichen . Ausgabeparameter sollten immer eine maximale Pufferlänge haben. Eingabeparameter sind normalerweise NUL-terminiert (es gibt Ausnahmen, aber meiner Erfahrung nach selten).In C ist die Redewendung, dass Zeichenketten NUL-terminiert sind. Daher ist es sinnvoll, sich an die gängige Praxis zu halten. Es ist relativ unwahrscheinlich, dass Benutzer der Bibliothek nicht NUL-terminierte Zeichenketten haben (da diese zusätzliche Arbeit zum Drucken benötigen) Verwendung von printf und Verwendung in einem anderen Kontext). Die Verwendung einer anderen Saite ist unnatürlich und wahrscheinlich relativ selten.
Unter diesen Umständen erscheint mir Ihr Test auch etwas seltsam, da Sie für die korrekte Funktion (mit strlen) zunächst von einem NUL-terminierten String ausgehen. Sie sollten den Fall von nicht NUL-terminierten Zeichenfolgen testen, wenn Sie beabsichtigen, dass Ihre Bibliothek damit arbeitet.
quelle
Dein "Sicherheits" -Argument ist nicht wirklich gültig. Wenn Sie dem Benutzer nicht vertrauen, dass er Ihnen eine nullterminierte Zeichenfolge übergibt, wenn Sie dies dokumentiert haben (und was "die Norm" für einfaches C ist), können Sie der Länge, die sie Ihnen geben, auch nicht wirklich vertrauen (was sie auch tun werden) Wahrscheinlich kommen
strlen
Sie damit zurecht, wie Sie es tun, wenn sie es nicht zur Hand haben.Es gibt triftige Gründe, eine Länge zu fordern: Wenn Sie möchten, dass Ihre Funktionen mit Teilzeichenfolgen arbeiten, ist es möglicherweise viel einfacher (und effizienter), eine Länge zu übergeben, als dass der Benutzer etwas hin und her kopieren muss, um das Null-Byte zu erhalten am richtigen Ort (und riskieren dabei einzelne Fehler).
Die Möglichkeit, Codierungen zu verarbeiten, bei denen Nullbytes keine Abschlusszeichen sind, oder Zeichenfolgen mit eingebetteten Nullen (absichtlich), kann unter bestimmten Umständen hilfreich sein (hängt davon ab, was genau Ihre Funktionen tun).
Es ist auch praktisch, Daten (Arrays fester Länge) verarbeiten zu können, die nicht mit Nullen terminiert sind.
Kurz gesagt: Hängt davon ab, was Sie in Ihrer Bibliothek tun und welche Art von Daten Sie von Ihren Benutzern erwarten.
Dies hat möglicherweise auch einen Leistungsaspekt. Wenn Ihre Funktion die Länge der Zeichenfolge im Voraus kennen muss und Sie erwarten, dass Ihre Benutzer diese Informationen zumindest normalerweise bereits kennen, kann es einige Zyklen ersparen, wenn sie diese Informationen weitergeben (anstatt sie zu berechnen).
Wenn Ihre Bibliothek jedoch normale reine ASCII-Textzeichenfolgen erwartet und Sie keine übermäßigen Leistungseinschränkungen und ein sehr gutes Verständnis für die Interaktion Ihrer Benutzer mit Ihrer Bibliothek haben, ist das Hinzufügen eines Längenparameters keine gute Idee. Wenn der String nicht richtig terminiert ist, ist der Längenparameter wahrscheinlich genauso falsch. Ich denke nicht, dass Sie viel damit gewinnen werden.
quelle
Nein. Strings sind per Definition immer nullterminiert, die Stringlänge ist redundant.
Nicht nullterminierte Zeichendaten sollten niemals als "Zeichenfolge" bezeichnet werden. Die Verarbeitung (und das Herumwerfen von Längen) sollte normalerweise in einer Bibliothek gekapselt werden und nicht Teil der API. Es ist wahrscheinlich, dass die Länge als Parameter erforderlich ist, um einzelne strlen () -Aufrufe zu vermeiden.
Es ist nicht unsicher , dem Aufrufer einer API-Funktion zu vertrauen . undefiniertes Verhalten ist völlig in Ordnung, wenn dokumentierte Voraussetzungen nicht erfüllt sind.
Natürlich sollte eine gut gestaltete API keine Fallstricke enthalten und die korrekte Verwendung erleichtern. Und das bedeutet nur, dass es so einfach und unkompliziert wie möglich sein sollte, Redundanzen vermieden werden und die Konventionen der Sprache befolgt werden.
quelle
Sie sollten immer Ihre Länge um halten. Zum einen möchten Ihre Benutzer möglicherweise NULL-Werte enthalten. Und zweitens: Vergessen Sie nicht, dass dies
strlen
O (N) ist und dass Sie den gesamten String-Bye-Bye-Cache berühren müssen. Und drittens erleichtert dies das Weitergeben von Teilmengen - sie könnten beispielsweise weniger als die tatsächliche Länge ergeben.quelle
strlen
in einem Schleifentest verwendet.)Sie sollten zwischen dem Umgehen eines Strings und dem Umgehen eines Puffers unterscheiden .
In C werden Strings traditionell mit NUL terminiert. Es ist völlig vernünftig, dies zu erwarten. Daher ist es normalerweise nicht erforderlich, die Länge der Zeichenfolge zu überschreiten. es kann mit berechnet werden
strlen
werden.Beim Umfahren eines Puffers , insbesondere einen, in den geschrieben wird, sollten Sie die Puffergröße unbedingt mitgeben. Bei einem Zielpuffer kann der Angerufene so sicherstellen, dass der Puffer nicht überläuft. Bei einem Eingabepuffer kann der Angerufene vermeiden, über das Ende hinaus zu lesen, insbesondere wenn der Eingabepuffer beliebige Daten enthält, die von einer nicht vertrauenswürdigen Quelle stammen.
Es besteht möglicherweise eine gewisse Verwirrung, da sowohl Zeichenfolgen als auch Puffer vorhanden sein können
char*
und viele Zeichenfolgenfunktionen neue Zeichenfolgen erzeugen, indem sie in Zielpuffer schreiben. Einige Leute schließen daraus, dass String-Funktionen String-Längen annehmen sollten. Dies ist jedoch eine ungenaue Schlussfolgerung. Das Einfügen einer Größe in einen Puffer (unabhängig davon, ob dieser Puffer für Zeichenfolgen, Arrays von Ganzzahlen oder Strukturen verwendet wird) ist ein nützlicheres und allgemeineres Mantra.(Im Falle eines Strings aus einer nicht vertrauenswürdigen Quelle zu lesen (zB eine Netzwerkbuchse), ist es wichtig , eine Länge zu liefern , da der Eingang möglicherweise nicht NUL-terminiert werden. Allerdings sollten Sie nicht die Eingabe betrachten eine Zeichenfolge sein. Sie sollte es als willkürlicher Datenpuffer behandeln, der eine Zeichenfolge enthalten könnte (aber Sie wissen es nicht, bis Sie es tatsächlich validieren), daher folgt dies weiterhin dem Prinzip, dass Puffer zugeordnete Größen haben sollten und dass Zeichenfolgen sie nicht benötigen.)
quelle
Wenn Funktionen hauptsächlich mit String-Literalen verwendet werden, kann der Aufwand für den Umgang mit expliziten Längen durch das Definieren einiger Makros minimiert werden. Beispiel für eine gegebene API-Funktion:
man könnte ein Makro definieren:
und rufen Sie es dann wie folgt auf:
Während es möglich sein mag, "kreative" Dinge zu entwickeln, die das zu kompilierende Makro übergehen, aber nicht funktionieren, sollte die Verwendung von
""
auf beiden Seiten der Zeichenkette bei der Bewertung von "sizeof" versehentliche Versuche zur Verwendung von Zeichen auffangen andere Zeiger als zerlegte String-Literale [""
Ohne diese würde ein Versuch, einen Zeichenzeiger zu übergeben, fälschlicherweise die Länge als Größe eines Zeigers minus eins angeben.Ein alternativer Ansatz in C99 wäre, einen Strukturtyp "Zeiger und Länge" zu definieren und ein Makro zu definieren, das ein Zeichenfolgenliteral in ein zusammengesetztes Literal dieses Strukturtyps umwandelt. Beispielsweise:
Beachten Sie, dass Sie bei Verwendung eines solchen Ansatzes solche Strukturen als Wert übergeben sollten, anstatt ihre Adressen weiterzugeben. Ansonsten so etwas wie:
kann fehlschlagen, da die Lebensdauer von zusammengesetzten Literalen am Ende ihrer beigefügten Anweisungen enden würde.
quelle