Bestimmen der sprintf-Puffergröße – was ist der Standard?

Lesezeit: 10 Minuten

Benutzeravatar von Dominic Bou-Samra
Dominic Bou-Samra

Beim Konvertieren eines int wie folgt:

char a[256];
sprintf(a, "%d", 132);

wie kann man am besten feststellen wie groß a sollte sein? Ich nehme an, es ist in Ordnung, es manuell einzustellen (wie ich es überall gesehen habe), aber wie groß sollte es sein? Was ist der größtmögliche int-Wert auf einem 32-Bit-System, und gibt es eine knifflige Methode, dies im Handumdrehen zu bestimmen?

  • Wenn die Verwendung von C++ stattdessen eine Option ist, können Sie natürlich einfach std::string und std::stringstream verwenden, um das zu erreichen, was Sie wollen, ohne auch nur über Speicheranforderungen nachzudenken. Aber das kommt wirklich darauf an. Ich weiß, dass die Frage für C ist, aber vielleicht könnte das trotzdem nützlich sein.

    – Robert Massaioli

    13. Oktober 2010 um 0:33 Uhr

  • @Robert: Wenn stattdessen die Verwendung von Python eine Option ist, können Sie verwenden str ;-p

    – Steve Jessop

    13. Oktober 2010 um 0:35 Uhr


  • @Robert: Es ist für eine Uni-Aufgabe. Ich habe C vor dieser Einheit gehasst. Jetzt liebe ich die Einfachheit. Es ist unversöhnlich, aber sehr befriedigend, mit einer großen Lernkurve, da ich mich normalerweise mit Python/Managed Langs versuche.

    – Dominic Bou-Samra

    13. Oktober 2010 um 1:11 Uhr

  • @Robert: Ich würde argumentieren, dass keine andere Sprache als C oder Assembly Ihre Anforderungen erfüllen könnte, wenn Sie über diese Art von Problem nachdenken müssen. Jede andere Sprache hat eine monströse, schwer vorhersehbare Stack-Nutzung, Heap-Fragmentierung usw.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    13. Oktober 2010 um 2:19 Uhr

  • In der GNU-Welt haben Sie asprintfdie intern wird malloc die benötigte Speicherkapazität.

    – utopischer Himmel

    1. April 2013 um 20:39 Uhr

Benutzeravatar von Daniel Standage
Daniel Steh

Einige hier argumentieren, dass dieser Ansatz übertrieben ist, und für die Umwandlung von Ints in Strings bin ich eher geneigt, dem zuzustimmen. Wenn jedoch keine vernünftige Grenze für die Zeichenfolgengröße gefunden werden kann, habe ich diesen Ansatz gesehen und selbst verwendet.

int size = snprintf(NULL, 0, "%d", 132);
char * a = malloc(size + 1);
sprintf(a, "%d", 132);

Ich werde aufschlüsseln, was hier vor sich geht.

  1. In der ersten Zeile wollen wir bestimmen, wie viele Zeichen wir brauchen. Die ersten 2 Argumente zu snprintf Sagen Sie ihm, dass ich 0 Zeichen des Ergebnisses schreiben möchte NULL. Wenn wir dies tun, snprintf schreibt eigentlich keine Zeichen, sondern gibt einfach die Anzahl der Zeichen zurück, die geschrieben worden wären. Das wollten wir.
  2. In der zweiten Zeile weisen wir a dynamisch Speicher zu char Zeiger. Achten Sie darauf und addieren Sie 1 zur erforderlichen Größe (für die abschließende \0 Abschlusszeichen).
  3. Jetzt, da der char Zeiger, können wir sicher verwenden sprintf um die Ganzzahl in die zu schreiben char Zeiger.

Natürlich können Sie es kürzer machen, wenn Sie möchten.

char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1);
sprintf(a, "%d", 132);

Sofern es sich nicht um ein “Quick and Dirty”-Programm handelt, möchten Sie immer sicherstellen, dass Sie den Speicher freigeben, mit dem Sie angerufen haben malloc. Hier wird der dynamische Ansatz mit C kompliziert. Allerdings, IMHO, wenn Sie nicht riesig zuweisen möchten char Hinweise, wenn Sie die meiste Zeit nur einen sehr kleinen Teil davon verwenden werden, dann denke ich nicht, dass dies ein schlechter Ansatz ist.

  • Eigentlich könnte man sie oft einfach gebrauchen alloca Anstatt von malloc. Und wenn der resultierende Code immer noch zu aufgebläht ist, erstellen Sie ein Makro dafür.

    – diejh

    19. August 2013 um 14:31 Uhr

  • Wie tragbar ist alloca? Es ist definitiv kein ANSI C.

    – Daniel Standage

    19. August 2013 um 19:24 Uhr

  • Sicherlich besteht die portable C99-Alternative zu alloca nur darin, ein Array mit variabler Länge zu verwenden? int size = ...; char a[size+1]; sprintf(...?

    – Tommi

    19. April 2016 um 12:50 Uhr


  • Vergessen Sie nicht, den Fall zu behandeln, in dem malloc() NULL zurückgibt, es sei denn, es ist Ihnen egal, ob Ihr Programm abstürzt oder unsicher ist. Dies gilt insbesondere dann, wenn die Größe von Eingaben von außerhalb des Programms abhängt.

    – Jeff Learmann

    14. August 2017 um 20:28 Uhr

  • Auf jeden Fall +1, ich verstehe nicht, warum dies nicht die akzeptierte Antwort ist. Es ist genau das, was die Manpage erklärt.

    – Marco Bonelli

    18. Januar 2019 um 15:33 Uhr

Benutzeravatar von Regis Portalez
Regis Portalez

Es ist möglich, die Lösung von Daniel Standage mit einer beliebigen Anzahl von Argumenten zum Laufen zu bringen vsnprintf das ist in C++11/C99.

int bufferSize(const char* format, ...) {
    va_list args;
    va_start(args, format);
    int result = vsnprintf(NULL, 0, format, args);
    va_end(args);
    return result + 1; // safe byte for \0
}

Wie angegeben in c99-StandardAbschnitt 7.19.6.12 :

Die Funktion vsnprintf gibt die Anzahl der Zeichen zurück, die geschrieben worden wären, wenn n ausreichend groß gewesen wäre, ohne das abschließende Nullzeichen, oder einen negativen Wert, wenn ein Codierungsfehler aufgetreten ist.

  • Es ist reines C99 zu.

    – fshp

    19. Februar 2017 um 23:55 Uhr


  • Vielen Dank. Nicht einmal bemerkt. Meine Antwort aktualisiert.

    – Regis Portalez

    20. Februar 2017 um 7:06 Uhr


  • _vscprintf ist bessere Lösung. Schauen Sie sich – stackoverflow.com/a/9369242/987850 an

    – 23W

    5. Juli 2017 um 9:10 Uhr


  • _vscprintf ist nur Microsoft.

    – Regis Portalez

    5. Juli 2017 um 9:18 Uhr

Benutzeravatar von Steve Jessop
Steve Jessop

Die maximal mögliche Anzahl von Bits in einem Int ist CHAR_BIT * sizeof(int)und eine Dezimalziffer ist mindestens 3 Bit “wert”, also eine lockere Obergrenze für den Platz, der für eine beliebige Ziffer erforderlich ist int ist (CHAR_BIT * sizeof(int) / 3) + 3. Dieses +3 ist eins dafür, dass wir beim Dividieren abgerundet haben, eins für das Vorzeichen, eins für das Null-Terminator.

Wenn Sie mit “auf einem 32-Bit-System” meinen, dass Sie es wissen int 32 Bit ist, dann benötigen Sie 12 Byte. 10 für die Ziffern, eine für das Vorzeichen, eine für das Null-Terminator.

In Ihrem speziellen Fall, wo sich das zu konvertierende int befindet 132, benötigen Sie 4 Byte. Badum, tisch.

Wenn Puffer mit fester Größe mit einer vernünftigen Grenze verwendet werden können, sind sie die einfachere Option. Ich behaupte nicht ganz so demütig, dass die obige Grenze angemessen ist (13 Bytes statt 12 für 32 Bit intund 23 Bytes statt 21 für 64 Bit int). Aber für schwierige Fälle könnte man in C99 einfach anrufen snprintf um die größe zu bekommen, dann malloc so viel. Das ist für einen so einfachen Fall wie diesen übertrieben.

  • Verwenden malloc denn das ist lächerlich. Es macht Ihren Code zu kompliziert, indem es einen Fehlerfall hinzufügt, den Sie überprüfen müssen – und was tun Sie, wenn es fehlschlägt?!? Verwenden Sie einfach einen Puffer mit der richtigen Größe, wie Sie es erklärt haben.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    13. Oktober 2010 um 2:14 Uhr

  • @R: Die Verwendung von malloc ist nicht lächerlich, wenn die Person, die den Code schreibt, daran interessiert ist, die Grundlagen der Sprache selbst zu lernen oder zu kennen. Den Leuten zu sagen, dass sie einfach std::string verwenden sollen, weil die ganze harte Arbeit bereits geleistet wurde, ist bis zu diesem Punkt ignorant; dh wissen wollen, wie die Dinge sozusagen unter der Haube funktionieren. Vielleicht möchte der Originalposter wissen, wie std::string tut, was es tut?

    – Erich

    28. Juni 2011 um 1:41 Uhr

  • @Eric: Diese Frage bezieht sich auf C, hat nichts damit zu tun std::string.

    – Steve Jessop

    28. Juni 2011 um 9:28 Uhr


  • Das ist gefährlich – ein klassisches potenzielles Pufferüberlaufszenario. Die genaue Ausgabe der printf Funktionsfamilie hängt vom Gebietsschema ab. Beispielsweise kann ein Gebietsschema das Tausendertrennzeichen festlegen.

    – Brett Hale

    26. Juli 2014 um 5:26 Uhr

  • @Brett: Du hast mich für eine Minute dorthin gebracht, aber natürlich %d verwendet kein Tausendertrennzeichen. %'d würde, aber das ist nicht die Frage.

    – Steve Jessop

    26. Juli 2014 um 8:54 Uhr


Benutzeravatar von HopeItHelps
Ich hoffe es hilft

Wie ich sehe, ist dieses Gespräch ein paar Jahre alt, aber ich habe es gefunden, als ich versuchte, eine Antwort für MS VC++ zu finden, wo snprintf nicht verwendet werden kann, um die Größe zu finden. Ich werde die Antwort posten, die ich endlich gefunden habe, falls es jemand anderem hilft:

VC++ hat die Funktion _scprintf speziell, um die Anzahl der benötigten Zeichen zu finden.

Benutzeravatar von Pegasus Epsilon
Pegasus Epsilon

Wenn Sie eine einfache Ganzzahl und nichts anderes drucken, gibt es eine viel einfachere Möglichkeit, die Größe des Ausgabepuffers zu bestimmen. Zumindest rechnerisch einfacher, der Code ist etwas stumpf:

char *text;
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2);

log10(value) gibt die Anzahl der Stellen (minus eins) zurück, die zum Speichern eines positiven Werts ungleich Null zur Basis 10 erforderlich sind. Bei Zahlen kleiner als eins gerät es etwas aus dem Ruder, also spezifizieren wir abs() und codieren eine spezielle Logik für Null (der ternäre Operator, test ? truecase : falsecase). Fügen Sie einen für das Leerzeichen hinzu, um das Vorzeichen einer negativen Zahl (Wert < 0) zu speichern, einen, um die Differenz von log10 auszugleichen, und einen weiteren für den Nullterminator (für eine Gesamtsumme von 2), und Sie haben gerade gerechnet die genaue Menge an Speicherplatz, die für eine bestimmte Zahl benötigt wird, ohne snprintf() oder Äquivalente aufzurufen zweimal um die Arbeit zu erledigen. Außerdem verbraucht es im Allgemeinen weniger Speicher als der INT_MAX benötigt.

Wenn Sie jedoch sehr schnell viele Zahlen drucken müssen, machen Sie sich die Mühe, den INT_MAX-Puffer zuzuweisen und dann stattdessen wiederholt darauf zu drucken. Weniger Speicher-Thrashing ist besser.

Beachten Sie auch, dass Sie das (double) im Gegensatz zu einem (float) möglicherweise nicht benötigen. Ich habe nicht nachgesehen. Ein solches Hin- und Herwerfen kann auch ein Problem sein. YMMV auf all das. Funktioniert bei mir aber super.

  • Nehmen wir an, Ihre Ganzzahl ist INT_MIN. Dann abs(val) ist INT_MIN, log10 darauf angewendet gibt NaN zurück und wandelt NaN in um int ist undefiniertes Verhalten. Wenn Sie außerdem 64-Bit-Ganzzahlen drucken, sind die größten von ihnen nicht genau als darstellbar double.

    – Pascal Cuoq

    13. Juni 2014 um 17:08 Uhr

  • @Pascal Cuoq – Interessante Eigenart, danke dafür. Ich denke, die Lösung ist nicht wirklich vollständig, ohne zu prüfen, ob wir versuchen, Speicher für die Zeichenfolgendarstellung von INT_MIN zuzuweisen, aber ich fühle mich faul, also überlasse ich das als Übung für jeden, der sich wirklich dafür interessiert . — PS “undefiniertes Verhalten” ist eine Untertreibung. In GCC printf(“%d\n”, (int)log10((double)abs(-2147483647 – 1))); spuckt “-2147483648” statt der erwarteten “9” aus — wie seltsam.

    – Pegasus Epsilon

    16. Januar 2015 um 14:02 Uhr


Benutzeravatar von EboMike
EboMike

Zunächst einmal ist sprintf der Teufel. Wenn überhaupt, verwenden Sie snprintf, sonst riskieren Sie, Speicher zu zerstören und Ihre App zum Absturz zu bringen.

Was die Puffergröße betrifft, so ist es wie bei allen anderen Puffern – so klein wie möglich, so groß wie nötig. In Ihrem Fall haben Sie eine vorzeichenbehaftete Ganzzahl, also nehmen Sie die größtmögliche Größe und fügen Sie ruhig ein wenig Sicherheitspolsterung hinzu. Es gibt keine “Standardgröße”.

Es ist auch unabhängig davon, auf welchem ​​​​System Sie arbeiten. Wenn Sie den Puffer auf dem Stapel definieren (wie in Ihrem Beispiel), hängt dies von der Größe des Stapels ab. Wenn Sie den Thread selbst erstellt haben, haben Sie die Stapelgröße selbst bestimmt, kennen also die Grenzen. Wenn Sie eine Rekursion oder einen Deep-Stack-Trace erwarten, müssen Sie ebenfalls besonders vorsichtig sein.

  • Nehmen wir an, Ihre Ganzzahl ist INT_MIN. Dann abs(val) ist INT_MIN, log10 darauf angewendet gibt NaN zurück und wandelt NaN in um int ist undefiniertes Verhalten. Wenn Sie außerdem 64-Bit-Ganzzahlen drucken, sind die größten von ihnen nicht genau als darstellbar double.

    – Pascal Cuoq

    13. Juni 2014 um 17:08 Uhr

  • @Pascal Cuoq – Interessante Eigenart, danke dafür. Ich denke, die Lösung ist nicht wirklich vollständig, ohne zu prüfen, ob wir versuchen, Speicher für die Zeichenfolgendarstellung von INT_MIN zuzuweisen, aber ich fühle mich faul, also überlasse ich das als Übung für jeden, der sich wirklich dafür interessiert . — PS “undefiniertes Verhalten” ist eine Untertreibung. In GCC printf(“%d\n”, (int)log10((double)abs(-2147483647 – 1))); spuckt “-2147483648” statt der erwarteten “9” aus — wie seltsam.

    – Pegasus Epsilon

    16. Januar 2015 um 14:02 Uhr


Aruns Benutzeravatar
Arun

Es ist gut, dass Sie sich Sorgen um die Puffergröße machen. Um diesen Gedanken in Code umzusetzen, würde ich verwenden snprintf

snprintf( a, 256, "%d", 132 );

oder

snprintf( a, sizeof( a ), "%d", 132 );  // when a is array, not pointer

  • snprintf löst nur das halbe Problem. Wenn Sie nicht sicher sind, ob Ihr Puffer groß genug ist, müssen Sie verwenden snprintf um die erforderliche Größe zu testen oder sicherzustellen, dass Ihr Code keine Fehler enthält, wenn die Ausgabe abgeschnitten wird. Steves Antwort ist viel besser.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    13. Oktober 2010 um 2:15 Uhr

1409790cookie-checkBestimmen der sprintf-Puffergröße – was ist der Standard?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy