Umgang mit Zeichenpuffern

7

Als C ++ - Programmierer muss ich manchmal mit Speicherpuffern umgehen, die Techniken von C verwenden. Zum Beispiel:

%Vor%

Oder in Windows:

%Vor%

Im obigen Beispiel erstelle ich normalerweise lokale Puffer (ein lokales stack-allokiertes Char-Array). Es gibt jedoch viele mögliche Variationen und deshalb bin ich sehr an Ihren Antworten auf die folgenden Fragen interessiert:

  • Übergibt den Puffer als &buffer[0] besseren Programmierstil als die Übergabe von buffer ? (Ich bevorzuge &buffer[0] .)
  • Gibt es eine maximale Größe, die für Stapelspeicherpuffer als sicher gilt?
    • Update: Ich meine zum Beispiel den höchsten Wert, der für plattformübergreifende Desktop-Anwendungen auf Mac-, Windows-, Linux-Desktops (nicht für Mobilgeräte!) als sicher gelten kann.
  • Ist ein statischer Puffer ( static char buffer[N]; ) schneller? Gibt es andere Argumente dafür oder dagegen?
  • Wenn Sie statische Puffer verwenden, können Sie den Rückgabetyp const char * verwenden. Ist das (allgemein) eine gute oder eine schlechte Idee? (Mir ist klar, dass der Anrufer seine eigene Kopie erstellen muss, um zu vermeiden, dass der nächste Anruf den vorherigen Rückgabewert ändert.)
  • Was ist mit der Verwendung von static char * buffer = new char[N]; , löschen Sie niemals den Puffer und verwenden Sie ihn bei jedem Aufruf erneut.
  • Ich verstehe, dass die Heap-Zuweisung verwendet werden sollte, wenn (1) mit großen Puffern umgehen oder (2) die maximale Puffergröße zur Kompilierungszeit unbekannt ist. Gibt es noch andere Faktoren, die bei der Stapel- / Heap-Zuweisungsentscheidung eine Rolle spielen?
  • Sollten Sie die Varianten sprintf_s , memcpy_s , ... bevorzugen? (Visual Studio hat mich schon lange davon überzeugt, aber ich möchte eine zweite Meinung: p)
StackedCrooked 24.06.2010, 19:16
quelle

9 Antworten

4

Ich gehe davon aus, dass Ihr Interesse in erster Linie aus einer Performance-Perspektive kommt, da Lösungen wie Vektor, String, Wstring, etc. auch für die Interaktion mit C-APIs funktionieren. Ich empfehle zu lernen, wie man diese benutzt und wie man sie effizient benutzt. Wenn Sie es wirklich brauchen, können Sie sogar Ihren eigenen Speicherzuordner schreiben, um sie super schnell zu machen. Wenn Sie sicher sind, dass sie nicht das sind, was Sie brauchen, gibt es immer noch keine Entschuldigung dafür, dass Sie keinen einfachen Wrapper schreiben, der diese Zeichenfolgenpuffer mit RAII für die dynamischen Fälle behandelt.

Damit nicht im Weg:

  

Übergibt den Puffer als & amp; Puffer [0]   besserer Programmierstil als das Übergeben   Puffer? (Ich bevorzuge & amp; Puffer [0].)

Nein. Ich würde diesen Stil als etwas weniger nützlich erachten (zugegebenermaßen hier subjektiv), da Sie ihn nicht zum Übergeben eines Null-Puffers verwenden können und daher Ausnahmen von Ihrem Stil machen müssen, um Zeiger auf Arrays zu übergeben, die Null sein können. Dies ist erforderlich, wenn Sie Daten von std :: vector an eine C-API übergeben, die jedoch einen Zeiger erwartet.

  

Gibt es eine maximale Größe?   als sicher für den zugewiesenen Stapel angesehen   Puffer?

Dies hängt von Ihren Plattform- und Compiler-Einstellungen ab. Einfache Faustregel: Wenn Sie Zweifel haben, ob Ihr Code den Stack überläuft, schreiben Sie ihn auf eine Weise, die nicht möglich ist.

  

Ist ein statischer Puffer (statische char   Puffer [N];) schneller? Sind da irgendwelche   andere Argumente dafür oder dagegen?

Ja, es gibt ein großes Argument dagegen, und das bedeutet, dass Ihre Funktion nicht länger einspringt. Wenn Ihre Anwendung zu Multithread wird, sind diese Funktionen nicht Thread-sicher. Selbst in einer single-threaded-Anwendung kann der gemeinsame Zugriff auf denselben Puffer beim rekursiven Aufruf dieser Funktionen zu Problemen führen.

  

Was ist mit der Verwendung von statischen char * Puffer   = neuer Buchstabe [N]; und nie den Puffer löschen? (Den gleichen Puffer wiederverwenden   ruf an.)

Wir haben immer noch die gleichen Probleme mit der Re-Entrance.

  

Ich verstehe diese Heap-Zuweisung   sollte verwendet werden, wenn (1) Umgang mit   große Puffer oder (2) maximaler Puffer   Größe ist zur Kompilierzeit unbekannt. Sind   da spielen noch andere Faktoren mit   die Stapel / Heap-Zuweisungsentscheidung?

Das Abwickeln von Stapeln zerstört Objekte auf dem Stapel. Dies ist besonders wichtig für die Ausnahmesicherheit. Selbst wenn Sie Speicher auf dem Heap innerhalb einer Funktion zuweisen, sollte er im Allgemeinen von einem Objekt auf dem Stack verwaltet werden (z. B. Smart Pointer). /// @ siehe RAII.

  

Sollten Sie die sprintf_s bevorzugen,   memcpy_s, ... Varianten? (Visual Studio   hat versucht, mich davon zu überzeugen   für eine lange Zeit, aber ich will eine Sekunde   Meinung: p)

MS hatte Recht damit, dass diese Funktionen sicherere Alternativen sind, da sie keine Pufferüberlaufprobleme haben. Wenn Sie jedoch solchen Code schreiben (ohne Varianten für andere Plattformen zu schreiben), wird Ihr Code mit Microsoft verheiratet sein nicht tragbar sein.

  

Wenn Sie statische Puffer verwenden, können Sie verwenden   Rückgabetyp const char *. Ist das   (allgemein) eine gute oder eine schlechte Idee? (ICH   Erkenne, dass der Anrufer benötigt wird   seine eigene Kopie zu machen, um das zu vermeiden   Der nächste Anruf würde den vorherigen ändern   Rückgabewert.)

Ich würde fast immer sagen, dass Sie const char * für Rückgabetypen für eine Funktion verwenden möchten, die einen Zeiger auf einen Zeichenpuffer zurückgibt. Eine Funktion, die ein veränderbares Zeichen zurückgibt, ist im Allgemeinen verwirrend und problematisch. Entweder gibt es eine Adresse an globale / statische Daten zurück, die nicht an erster Stelle verwendet werden sollten (siehe oben), lokale Daten einer Klasse (wenn es sich um eine Methode handelt). In diesem Fall ruiniert es die Fähigkeit der Klasse Invarianten pflegen, indem Clients erlaubt werden, sie zu manipulieren, wie sie wollen (zB gespeicherte Zeichenfolge muss immer gültig sein), oder Speicher zurückgeben, der durch einen an die Funktion übergebenen Zeiger angegeben wurde (der einzige Fall, in dem man vernünftigerweise argumentieren könnte, sollte zurückgegeben werden).

    
stinky472 24.06.2010, 19:44
quelle
8
  1. Halten Sie sich von statischen Puffern fern, wenn Sie Ihren Code immer wieder verwenden möchten.

  2. verwenden Sie snprintf () anstelle von sprintf (), damit Sie Pufferüberläufe steuern können.

  3. Sie wissen nie, wie viel Speicherplatz im Rahmen Ihres Anrufs übrig ist - also ist keine Größe technisch "sicher". Sie haben mit den meisten Spielen viel Spielraum. Aber dieses eine Mal wird dir gut tun. Ich verwende eine Faustregel, um niemals Arrays auf den Stack zu legen.

  4. Lassen Sie den Client den Puffer besitzen und übergeben Sie ihn und seine Größe an Ihre Funktion. Das macht es einspringend und lässt keine Zweideutigkeit darüber übrig, wer die Lebensdauer des Puffers verwalten muss.

  5. Wenn Sie mit String-Daten arbeiten, überprüfen Sie Ihre String-Funktionen, um sicherzustellen, dass sie enden, besonders wenn sie das Ende des Puffers erreichen. Die C-Bibliothek ist sehr inkonsistent, wenn es darum geht, die Terminierung von Strings über die verschiedenen Funktionen hinweg zu handhaben.

Amardeep AC9MF 24.06.2010 19:27
quelle
3
  • Es liegt an Ihnen, nur buffer ist knapper, aber wenn es ein vector wäre, müssten Sie trotzdem &buffer[0] machen.
  • Hängt von Ihrer beabsichtigten Plattform ab.
  • Ist das wichtig? Haben Sie festgestellt, dass es ein Problem ist? Schreiben Sie den Code, der am einfachsten zu lesen und zu pflegen ist, bevor Sie sich Gedanken darüber machen, ob Sie ihn in etwas schneller verschleiern können. Aber für was es wert ist, ist die Zuweisung auf dem Stapel sehr schnell (Sie ändern nur den Stapelzeigerwert.)
  • Sie sollten std::string verwenden. Wenn die Leistung zu einem Problem wird, können Sie dynamische Zuordnungen reduzieren, indem Sie nur den internen Puffer zurückgeben. Die Schnittstelle std::string return ist jedoch viel netter und sicherer und die Leistung ist Ihre letzte Sorge.
  • Das ist ein Speicherleck. Viele werden argumentieren, dass das in Ordnung ist, da das OS es sowieso frei gibt, aber ich finde es furchtbar übel, Sachen einfach auszulaufen. Verwenden Sie eine statische std::vector , Sie sollten niemals eine rohe Zuweisung vornehmen! Wenn Sie sich selbst in eine Position bringen, in der Sie möglicherweise auslaufen (weil es explizit ausgeführt werden muss), tun Sie es falsch.
  • Ich denke, Ihr (1) und (2) decken es gerade ab. Die dynamische Zuweisung ist fast immer langsamer als die Stapelzuweisung, aber Sie sollten mehr darüber besorgt sein, was in Ihrer Situation sinnvoll ist.
  • Sie sollten diese überhaupt nicht benutzen. Verwenden Sie std::string , std::stringstream , std::copy , usw.
GManNickG 24.06.2010 19:26
quelle
3

Sie haben viele Fragen! Ich werde mein Bestes tun, um einem Paar zu antworten und dir einen Platz zu geben, wo du nach den anderen Ausschau halten kannst.

  

Gibt es eine maximale Größe, die für Stapelspeicherpuffer als sicher gilt?

Ja, aber die Stack-Größe selbst hängt von der Plattform ab, an der Sie arbeiten. Siehe Wann machst du dir Gedanken über die Stapelgröße? für eine sehr ähnliche Frage .

  

Ist ein statischer Zeichenpuffer [N]; schneller? Sind   Da gibt es noch andere Argumente für oder   dagegen?

Die Bedeutung von static hängt davon ab, wo der Puffer deklariert ist, aber ich nehme an, dass Sie von einer static sprechen, die in einer Funktion deklariert ist. Daher wird sie nur einmal initialisiert. In Funktionen, die oft aufgerufen werden, kann die Verwendung von statischen Puffern eine gute Idee sein, um einen Stapelüberlauf zu verhindern, aber bedenken Sie auch, dass das Zuweisen von Puffern eine billige Operation ist. Außerdem ist es mit statischen Puffern viel schwieriger, mit mehreren Threads zu arbeiten.

Antworten zu den meisten anderen Fragen finden Sie unter Große Puffer vs Große statische Puffer, gibt es einen Vorteil? .

    
Justin Ardini 24.06.2010 19:27
quelle
1
Übergibt der Puffer als & amp; Puffer [0] besseren Programmierstil als übergeben Puffer? (Ich bevorzuge & amp; Puffer [0].)

&buffer[0] macht den Code für mich weniger lesbar. Ich muss kurz innehalten und mich wundern, warum jemand es benutzt hat, anstatt einfach buffer zu übergeben. Manchmal müssen Sie &buffer[0] verwenden (wenn buffer ein std::vector ist), ansonsten bleiben Sie beim Standard-C-Stil.

Gibt es eine maximale Größe, die für Stapelspeicherpuffer als sicher gilt?

Ich bezweifle, dass es ein praktisches Limit gibt, solange Sie den Stack vernünftig benutzen. Ich hatte nie Probleme in meiner Entwicklung.

Wenn ich MSDN richtig lese, threads unter Windows Standard ist 1 MB Stapelgröße. Dies ist konfigurierbar. Andere Plattformen haben andere Grenzen.

Ist ein statischer Zeichenpuffer [N]; schneller? Gibt es noch andere Argumente dafür oder dagegen?

Auf der einen Seite verringert sich möglicherweise die Notwendigkeit, Speicherseiten für den Stack zu speichern, sodass Ihre App möglicherweise schneller ausgeführt wird. Wenn Sie andererseits zum BSS-Segment oder Ähnlichem wechseln, wird die Cache-Position im Vergleich zum Stapel möglicherweise reduziert langsamer laufen. Ich bezweifle ernsthaft, dass du den Unterschied bemerken würdest.

Die Verwendung von static ist nicht threadsicher, während der Stapel verwendet wird. Das ist ein großer Vorteil für den Stack. (Selbst wenn Sie nicht denken, dass Sie Multithread werden, warum das Leben schwerer machen, wenn sich das in der Zukunft ändert?)

Wenn Sie statische Puffer verwenden, können Sie Ihre Funktion mit dem Rückgabetyp const char * zurückgeben lassen. Ist das eine gute Idee? (Mir ist klar, dass der Anrufer seine eigene Kopie erstellen muss, um zu vermeiden, dass der nächste Anruf den vorherigen Rückgabewert ändert.)

Const Korrektheit ist immer eine gute Sache.

Das Zurückgeben von Zeigern auf statische Puffer ist fehleranfällig; ein späterer Aufruf könnte es modifizieren, ein anderer Thread könnte es modifizieren usw. Verwenden Sie stattdessen std::string oder anderen automatisch zugewiesenen Speicher (auch wenn Ihre Funktion intern mit char-Puffern umgehen muss, wie zB Ihr GetCurrentDirectory -Beispiel).

Was ist mit der Verwendung von statischen char * buffer = new char [N]; und nie den Puffer löschen? (Den gleichen Puffer bei jedem Anruf wiederverwenden.)

Weniger effizient als nur die Verwendung von static char buffer[N] , da Sie eine Heap-Zuweisung benötigen.

Ich verstehe, dass Heap-Zuordnung verwendet werden sollte, wenn (1) mit großen Puffern oder (2) maximale Puffergröße zur Kompilierzeit unbekannt ist. Gibt es weitere Faktoren, die bei der Stapel- / Heap-Allokationsentscheidung eine Rolle spielen?

Siehe Justin Ardinis Antwort.

Sollten Sie die Varianten sprintf_s, memcpy_s, ... bevorzugen? (Visual Studio hat mich schon lange davon überzeugt, aber ich möchte eine zweite Meinung: p)

Dies ist eine Frage der Debatte. Ich persönlich denke, dass diese Funktionen eine gute Idee sind, und wenn Sie ausschließlich auf Windows abzielen, ist es von Vorteil, den bevorzugten Windows-Ansatz zu verwenden und diese Funktionen zu verwenden. (Und sie können relativ einfach neu implementiert werden, wenn Sie später etwas anderes als Windows anvisieren müssen, solange Sie sich nicht auf ihr Fehlerbehandlungsverhalten verlassen.) Andere denken, dass die sicheren CRT-Funktionen nicht sicherer sind als ordnungsgemäß verwendet C und andere Nachteile einführen; Wikipedia-Links zu einigen Argumenten gegen sie.

    
Josh Kelley 24.06.2010 19:36
quelle
1

Wenn eine Funktion Ihnen eine Methode zur Verfügung stellt, wie viele Zeichen zurückgegeben werden, verwenden Sie sie. Ihr Beispiel GetCurrentDirectory ist ein gutes Beispiel:

%Vor%

Dann können Sie ein dynamisch zugewiesenes Array (String oder Vektor) verwenden, um das Ergebnis zu erhalten:

%Vor%     
Mark Ransom 24.06.2010 19:39
quelle
1

1) buffer und &buffer[0] sollten gleichwertig sein.

2) Die Größe der Stackgröße hängt von Ihrer Plattform ab. Für die meisten einfachen Funktionen ist meine persönliche Faustregel etwas über ~ 256KB wird dynamisch deklariert; Es gibt keinen wirklichen Sinn oder Grund zu dieser Nummer, aber es ist nur meine eigene Konvention und es ist derzeit innerhalb der Standard-Stack-Größen für alle Plattformen, für die ich entwickle.

3) Statische Puffer sind nicht schneller oder langsamer (für alle Absichten und Zwecke). Der einzige Unterschied ist der Zugriffskontrollmechanismus. Der Compiler platziert im Allgemeinen statische Daten in einem separaten Abschnitt der Binärdatei als nicht statische Daten, aber es gibt keinen bemerkbaren / signifikanten Leistungsvorteil oder Nachteil. Der einzige Weg, um sicher zu sein, besteht darin, das Programm in beide Richtungen zu schreiben und zu programmieren (da viele der Geschwindigkeitsaspekte hier von Ihrer Plattform / Ihrem Compiler abhängen).

4) Geben Sie keinen const -Zeiger zurück, wenn der Aufrufer ihn ändern muss (dies verhindert den Punkt von const ). Verwenden Sie const für Funktionsparameter und Rückgabetypen genau dann, wenn sie nicht für eine Änderung vorgesehen sind. Wenn der Aufrufer den Wert ändern muss, ist es am besten, wenn der Aufrufer der Funktion einen Zeiger auf einen zuvor zugewiesenen Puffer (zusammen mit der Puffergröße) und die Funktion zum Schreiben der Daten in diesen Puffer übergibt. p>

5) Die Wiederverwendung eines Puffers kann zu einer Leistungsverbesserung für größere Puffer führen, da der Overhead umgangen wird, der jedes Mal den Aufruf von malloc / free oder new / delete erfordert. Sie laufen jedoch Gefahr, versehentlich alte Daten zu verwenden, wenn Sie vergessen, den Puffer jedes Mal zu löschen, oder Sie versuchen, zwei Kopien der Funktion parallel auszuführen. Wiederum ist der einzige wirkliche Weg, um sicher zu wissen, beide Wege zu versuchen und zu messen, wie lange der Code benötigt, um zu laufen.

6) Ein weiterer Faktor bei der Stapel- / Heap-Zuweisung ist das Scoping. Eine Stapelvariable verlässt den Gültigkeitsbereich, wenn die Funktion, in der sie sich befindet, zurückgibt, aber eine Variable, die dynamisch auf dem Heap zugewiesen wurde, kann sicher zum Aufrufer zurückgegeben werden oder beim nächsten Aufruf der Funktion aufgerufen werden (a la strtok ).

7) Ich würde gegen die Verwendung von sprintf_s , memcpy_s und Freunden empfehlen. Sie sind nicht Teil der Standardbibliothek und nicht übertragbar. Je mehr Sie diese Funktionen verwenden, desto mehr Arbeit benötigen Sie, wenn Sie Ihren Code auf einer anderen Plattform ausführen oder einen anderen Compiler verwenden möchten.

    
bta 24.06.2010 20:00
quelle
0
  • Buffer oder & amp; Buffer [0] ist genau das gleiche. Sie könnten sogar Buffer + 0 schreiben. Persönlich bevorzuge ich es, nur Buffer zu schreiben (und ich denke, dass die meisten Entwickler dies auch bevorzugen), aber das ist Ihre persönliche Entscheidung
  • Das Maximum hängt davon ab, wie groß und wie tief dein Stack ist. Wenn Sie bereits 100 Funktionen tief im Stapel haben, ist die maximale Größe kleiner. Wenn Sie C ++ verwenden können, könnten Sie eine Pufferklasse schreiben, die dynamisch auswählt, ob der Stack (für kleine Größen) oder der Heap (für große Größen) verwendet werden soll. Sie finden den Code unten.
  • Ein statischer Puffer ist schneller, da der Compiler den Speicherplatz für Sie reserviert. Ein Stapelpuffer ist auch schnell. Für einen Stapelpuffer muss die Anwendung nur den Stapelzeiger erhöhen. Für einen Heap-Puffer muss der Speichermanager freien Platz finden, das Betriebssystem nach neuem Speicher fragen, danach wieder freigeben, Buchführung durchführen, ...
  • Verwenden Sie nach Möglichkeit C ++ - Zeichenfolgen, um Speicherlecks zu vermeiden. Andernfalls muss der Anrufer wissen, ob er den Speicher anschließend freigeben muss oder nicht. Der Nachteil ist, dass C ++ - Zeichenfolgen langsamer sind als statische Puffer (da sie auf dem Heap zugeordnet sind).
  • Ich würde die Speicherzuweisung für globale Variablen nicht verwenden. Wann werden Sie es löschen? Und können Sie sicher sein, dass keine andere globale Variable den zugewiesenen Speicher benötigt (und verwendet werden muss, bevor Ihr statischer Puffer zugewiesen wird)?
  • Welche Art von Puffer Sie auch verwenden, versuchen Sie, die Implementierung vor dem Aufrufer Ihrer Funktion zu verbergen. Sie könnten versuchen, den Buffer-Pointer in einer Klasse zu verstecken, lassen Sie die Klasse sich merken, ob der Buffer dynamisch allokiert ist oder nicht (und sollte ihn daher in seinem Destruktor löschen oder nicht). Danach ist es einfach, den Typ des Puffers zu ändern, was nicht möglich ist, wenn Sie einfach einen char-Zeiger zurückgeben.
  • Persönlich bevorzuge ich die normalen sprintf-Varianten, aber das liegt wahrscheinlich daran, dass ich immer noch viel älteren Code habe und keine gemischte Situation möchte. Ziehen Sie in jedem Fall die Verwendung von snprintf in Betracht, wo Sie die Puffergröße übergeben können.

Code für dynamischen Stapel / Heap-Puffer:

%Vor%     
Patrick 24.06.2010 19:35
quelle
0

Is passing the buffer as &buffer[0] better programming style than passing buffer? (I prefer &buffer[0].)

Das hängt von den Codierungsstandards ab. Ich persönlich bevorzuge: buffer + index anstatt &buffer[index] , aber es ist Geschmackssache.

Is there a maximum size that is considered safe for stack allocated buffers?

Es hängt von der Stapelgröße ab. Wenn die für den Puffer benötigte Stapelmenge die verfügbare Menge auf dem Stapel übersteigt, führt dies zu einem Stapelüberlauf .

Is static char buffer[N]; faster? Are there any other arguments for or against it?

Ja, es sollte schneller sein. Siehe auch diese Frage: Ist es eine schlechte Übung, ein Array als Mid-Function zu deklarieren?

When using static buffers you can have your function return have the const char * return type. Is this a good idea? (I do realize that the caller will need to make his own copy to avoid that the next call would change the previous return value.)

Nicht sicher, was statische in diesem Fall bedeutet, aber:

  1. Wenn Variable auf Stapel deklariert ist ( char buf[100] ): Sie sollten nicht Referenzen auf Dinge zurückgeben, die auf dem Stapel deklariert sind. Sie werden bei der nächsten Funktionsaufruf / deklaration (z. B. wenn der Stapel erneut verwendet wird) verworfen.

  2. Wenn die Variable als statisch static deklariert ist, wird Ihr Code nicht-reentrant. strtok ist in diesem Fall ein Beispiel.

What about using static char * buffer = new char[N]; and never deleting the buffer? (Reusing the same buffer each call.)

Es ist eine Möglichkeit, obwohl nicht empfohlen, weil es Ihren Code nicht-reentrant macht.

I understand that heap allocation should be used when (1) dealing with large buffers or (2) maximum buffer size is unknown at compile time. Are there any other factors that play in the stack/heap allocation decision?

Die Stack-Größe des laufenden Threads ist zu klein, um der Stack-Deklaration (wie zuvor erwähnt) zu entsprechen.

Should you prefer the sprintf_s, memcpy_s, ... variants? (Visual Studio has been trying to convince me of this for a long time, but I want a second opinion :p )

Wenn Sie möchten, dass Ihr Code portabel ist: Nein. Aber der Aufwand beim Erstellen eines portablen Makros ist in diesem Fall ziemlich klein:

%Vor%     
INS 24.06.2010 19:39
quelle

Tags und Links