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:
&buffer[0]
besseren Programmierstil als die Übergabe von buffer
? (Ich bevorzuge &buffer[0]
.) static char buffer[N];
) schneller? Gibt es andere Argumente dafür oder dagegen? 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.) static char * buffer = new char[N];
, löschen Sie niemals den Puffer und verwenden Sie ihn bei jedem Aufruf erneut. sprintf_s
, memcpy_s
, ... bevorzugen? (Visual Studio hat mich schon lange davon überzeugt, aber ich möchte eine zweite Meinung: p) 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).
Halten Sie sich von statischen Puffern fern, wenn Sie Ihren Code immer wieder verwenden möchten.
verwenden Sie snprintf () anstelle von sprintf (), damit Sie Pufferüberläufe steuern können.
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.
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.
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.
buffer
ist knapper, aber wenn es ein vector
wäre, müssten Sie trotzdem &buffer[0]
machen. 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. 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. std::string
, std::stringstream
, std::copy
, usw. 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? .
Ü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.
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% 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.
Code für dynamischen Stapel / Heap-Puffer:
%Vor% 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:
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.
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%