2009-02-05 5 views
12

Ich arbeite in C++.Gibt es eine Möglichkeit festzustellen, wie viele Zeichen von sprintf geschrieben werden?

Ich möchte eine potenziell sehr lange formatierte Zeichenfolge mit Sprintf (speziell eine sichere gezählte Version wie _snprintf_s, aber die Idee ist die gleiche) schreiben. Die ungefähre Länge ist zur Kompilierzeit unbekannt, daher muss ich dynamisch zugewiesenen Speicher verwenden, anstatt mich auf einen großen statischen Puffer zu verlassen. Gibt es eine Möglichkeit zu bestimmen, wie viele Zeichen für einen bestimmten Sprintf-Aufruf benötigt werden, so dass ich immer sicher sein kann, dass ich einen ausreichend großen Puffer habe?

Mein Fallback ist, ich nehme nur die Länge der Formatzeichenfolge, verdopple es und versuche es. Wenn es funktioniert, großartig, wenn es nicht tut, verdopple ich einfach die Größe des Puffers und versuche es erneut. Wiederholen bis es passt. Nicht gerade die klügste Lösung.

Es sieht so aus, als ob C99 die Übergabe von NULL an snprintf unterstützt, um die Länge zu erhalten. Ich nehme an, ich könnte ein Modul erstellen, das diese Funktionalität umschließt, wenn auch sonst nichts, aber ich bin nicht verrückt nach dieser Idee.

Vielleicht könnte ein fprintf zu "/ dev/null"/"nul" stattdessen funktionieren? Irgendwelche anderen Ideen?

EDIT: Gibt es alternativ eine Möglichkeit den Sprintf zu "zerhacken", so dass er mitten in den Schreibvorgang geht? Wenn das möglich ist, könnte es den Puffer füllen, verarbeiten und dann von dort, wo er unterbrochen wurde, nachfüllen.

Antwort

0

Ich habe nach der gleichen Funktionalität gesucht, über die Sie sprechen, aber soweit ich weiß, ist etwas so Einfaches wie die C99-Methode in C++ nicht verfügbar, da C++ derzeit nicht die in C99 hinzugefügten Funktionen enthält (wie snprintf).

Ihre beste Wette ist wahrscheinlich, ein Stringstream-Objekt zu verwenden. Es ist ein bisschen mühsamer als ein klar geschriebener Sprintf-Call, aber es wird funktionieren.

+0

Nicht sicher, warum das war downvoted ... –

+0

Ich habe es nicht getan, aber ich kann vielleicht sehen, warum es getan wurde. Sie brauchen C99 nicht, da PD-Versionen von snprintf() da draußen sind. Oder vielleicht, weil die Frage speziell nach einer printf() Lösung und nicht nach stringstream gefragt wurde. Ich weiß nicht, ich habe es längst aufgegeben, Drive-by-Down-Wähler zu verstehen. – paxdiablo

2

Ich würde einen zweistufigen Ansatz verwenden. Im Allgemeinen wird ein großer Prozentsatz von Ausgabezeichenfolgen unter einem bestimmten Schwellenwert liegen und nur wenige werden größer sein.

Stufe 1, verwenden Sie einen angemessenen statischen Puffer wie 4K. Da snprintf() einschränken kann, wie viele Zeichen geschrieben werden, erhalten Sie keinen Pufferüberlauf. Was Sie werden erhalten von snprintf() zurückgegeben wird die Anzahl der Zeichen würde geschrieben haben, wenn Ihr Puffer groß genug gewesen wäre.

Wenn Ihr Anruf an snprintf() weniger als 4K zurückgibt, dann verwenden Sie den Puffer und beenden. Wie gesagt, sollte die überwiegende Mehrheit der Anrufe nur das tun.

Einige werden nicht und das ist, wenn Sie Stufe 2 eingeben. Wenn der Anruf an snprintf() nicht in den 4K-Puffer passt, wissen Sie zumindest jetzt, wie groß ein Puffer Sie benötigen.

Zuweisen, mit malloc(), ein Puffer groß genug, um es dann snprintf() es wieder zu diesem neuen Puffer zu halten. Wenn Sie mit dem Puffer fertig sind, befreien Sie ihn.

Wir arbeiteten an einem System in den Tagen vor snprintf() und wir erreichten das gleiche Ergebnis, indem wir ein Datei-Handle mit /dev/null verbunden und damit fprintf() verwenden./dev/null hat immer garantiert so viele Daten übernommen, wie Sie es angegeben haben, so dass wir tatsächlich die Größe daraus erhalten würden, und falls nötig, einen Puffer zuweisen.

Halten Sie in der Art, dass nicht alle Systeme snprintf() haben (zum Beispiel, ich verstehe es _snprintf() in Microsoft C ist), so können Sie die Funktion finden müssen, die die gleiche Arbeit tut, oder auf die fprintf /dev/null Lösung zurück.

Seien Sie auch vorsichtig, wenn die Daten zwischen der Größenprüfung snprintf() und dem tatsächlichen snprintf() zum Puffer geändert werden können (d. H. Für Threads auszählen). Wenn die Größen zunehmen, erhalten Sie eine Pufferüberlauf-Beschädigung.

Wenn Sie die Regel folgen die Daten, einmal an eine Funktion übergeben, die dieser Funktion gehört ausschließlich bis zurückgab, wird dies kein Problem sein.

+0

Leider ist die snprintf() - Funktion nicht Standard C++. Ich habe gerade versucht, es mit Visual Studio 2008 Express Edition zu verwenden, und der Compiler meldet snprintf nicht gefunden. – jasonmray

+0

ich denke, es ist _snprintf() in Microsoft C++ – FryGuy

+0

@ rubancache, das ist, wenn Sie die Lösung "fprintf zu/dev/null" verwenden. – paxdiablo

0

Werfen Sie einen Blick auf CodeProject: CString-clone Using Standard C++. Es verwendet die von Ihnen vorgeschlagene Lösung mit der Vergrößerung der Puffergröße.

 
// ------------------------------------------------------------------------- 
    // FUNCTION: FormatV 
    //  void FormatV(PCSTR szFormat, va_list, argList); 
    //
// DESCRIPTION: // This function formats the string with sprintf style format-specs. // It makes a general guess at required buffer size and then tries // successively larger buffers until it finds one big enough or a // threshold (MAX_FMT_TRIES) is exceeded. // // PARAMETERS: // szFormat - a PCSTR holding the format of the output // argList - a Microsoft specific va_list for variable argument lists // // RETURN VALUE: // -------------------------------------------------------------------------

void FormatV(const CT* szFormat, va_list argList) 
{ 
#ifdef SS_ANSI 

    int nLen = sslen(szFormat) + STD_BUF_SIZE; 
    ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList); 
    ReleaseBuffer(); 
#else 
    CT* pBuf   = NULL; 
    int nChars   = 1; 
    int nUsed   = 0; 
    size_type nActual = 0; 
    int nTry   = 0; 

    do 
    { 
     // Grow more than linearly (e.g. 512, 1536, 3072, etc) 

     nChars   += ((nTry+1) * FMT_BLOCK_SIZE); 
     pBuf   = reinterpret_cast<CT*>(_alloca(sizeof(CT)*nChars)); 
     nUsed   = ssnprintf(pBuf, nChars-1, szFormat, argList); 

     // Ensure proper NULL termination. 
     nActual   = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1); 
     pBuf[nActual+1]= '\0'; 


    } while (nUsed < 0 && nTry++ < MAX_FMT_TRIES); 

    // assign whatever we managed to format 

    this->assign(pBuf, nActual); 
#endif 
} 

21

The man page for snprintf sagt:

 
    Return value 
     Upon successful return, these functions return the number of 
     characters printed (not including the trailing '\0' used to end 
     output to strings). The functions snprintf and vsnprintf do not 
     write more than size bytes (including the trailing '\0'). If 
     the output was truncated due to this limit then the return value 
     is the number of characters (not including the trailing '\0') 
     which would have been written to the final string if enough 
     space had been available. Thus, a return value of size or more 
     means that the output was truncated. (See also below under 
     NOTES.) If an output error is encountered, a negative value is 
     returned. 

Was dies bedeutet, ist, dass Sie snprintf mit einer Größe von 0 Nichts nennen kann, wird geschrieben werden, und der Rückgabewert wird Ihnen sagen, wie viel Platz Sie Ihren zuweisen müssen string:

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...); 
+0

Es ist immer noch eine bessere Idee, zuerst auf eine Stapelvariable mit fester Größe auszugeben, da die überwiegende Mehrheit der Drucke unter einer bestimmten Größe liegt. Das bedeutet, dass die überwiegende Mehrheit die Print/Malloc/Print/Free nicht benötigt, sondern nur druckt. Nur die kleine Zahl über dem Limit braucht das volle Verhalten. – paxdiablo

+0

@Pax: Wahrscheinlich wahr, aber das ist eine Leistungsoptimierung. Es ist oft angebracht, aber nicht immer. Ein Beispiel, wenn das nicht der Fall ist: Sie haben nicht viel Stapelspeicher und Sie erwarten, dass die große Mehrheit der Ausdrucke größer ist als die Puffergröße, die Sie gerne auf den Stapel legen. Sie verwenden also immer den Heap. –

+0

Angesichts dieses Szenarios, warum nicht reservieren Sie einen _dedicated_ Teil des Speichers für die Drucke im Heap und profitieren von der Leistungsoptimierung? – user666412

5

Wie andere erwähnt haben, snprintf() wird die Anzahl der Zeichen in einem Puffer erforderlich Rück die Ausgabe aus wird abgeschnitten zu verhindern. Sie können es einfach mit einem Pufferlängenparameter von 0 aufrufen, um die erforderliche Größe zu erhalten, und dann einen entsprechend großen Puffer verwenden.

Für eine leichte Verbesserung der Effizienz können Sie es mit einem Puffer aufrufen, der groß genug für den Normalfall ist und nur einen zweiten Aufruf an snprintf(), wenn die Ausgabe abgeschnitten wird. Um sicherzustellen, dass die Puffer in diesem Fall ordnungsgemäß freigegeben werden, verwende ich oft ein auto_buffer<> Objekt, das den dynamischen Speicher für mich behandelt (und den Standardpuffer auf dem Stapel hat, um im Normalfall eine Heap-Zuweisung zu vermeiden)).

Wenn Sie einen Microsoft-Compiler verwenden, verfügt MS über einen nicht standardmäßigen _snprintf(), der schwerwiegende Einschränkungen von nicht immer Null Beendung des Puffers und nicht angibt, wie groß der Puffer sein sollte.

Um die Nicht-Unterstützung von Microsoft zu umgehen, verwende ich a nearly public domain snprintf() from Holger Weiss.

Natürlich, wenn Ihr Nicht-MS C oder C++ - Compiler snprintf() fehlt, sollte der Code aus dem obigen Link genauso gut funktionieren.

0

Da Sie C++ verwenden, müssen Sie keine Version von sprintf verwenden. Am einfachsten ist es, einen std :: ostringstream zu verwenden.

std::ostringstream oss; 
oss << a << " " << b << std::endl; 

oss.str() gibt eine std :: string mit dem Inhalt, was Sie oss geschrieben habe. Verwenden Sie oss.str().c_str(), um eine const char * zu erhalten. Es wird auf lange Sicht viel einfacher zu handhaben sein und Speicherlecks oder Pufferüberläufe beseitigen. Wenn Sie sich über Speicherprobleme wie C++ Sorgen machen, verwenden Sie die Sprache normalerweise nicht voll und Sie sollten Ihr Design überdenken.

+0

Ein Wort der Warnung: C++ hat eine Menge kleiner Extras in die Streaming-Funktionen verpackt, und diese können Sie auf eine größere Weise beißen. Insbesondere unterstützen die Streams Gebietsschemata, die die Formatierung Ihrer Zahlen ändern können. Zahlen, die in einem Stream ausgegeben wurden, der auf ein Gebietsschema festgelegt wurde, können nicht in einem Stream mit einem anderen Gebietsschema gelesen werden. Wenn Sie garantieren können, dass keine anderen Gebietsschemas jemals verwendet werden, dann geht es Ihnen gut. Wir wurden davon gebissen, weil wir eine DLL verwendet haben, die an eine Hostanwendung angeschlossen wurde, die Gebietsschemas verwendet. – AHelps

+0

http://unixhelp.ed.ac.uk/CGI/man-cgi?sprintf+3 Was war das mit Locales? –

1

Für was es wert ist, asprintf ist eine GNU-Erweiterung, die diese Funktionalität verwaltet.Er akzeptiert einen Zeiger als Ausgabeargument, zusammen mit einer Formatzeichenfolge und einer variablen Anzahl von Argumenten, und schreibt die Adresse eines korrekt zugewiesenen Puffers, der das Ergebnis enthält, in den Zeiger zurück.

Sie können es wie so verwenden:

#define _GNU_SOURCE 
#include <stdio.h> 

int main(int argc, char const *argv[]) 
{ 
    char *hi = "hello"; // these could be really long 
    char *everyone = "world"; 
    char *message; 
    asprintf(&message, "%s %s", hi, everyone); 
    puts(message); 
    free(message); 
    return 0; 
} 

hoffe, das hilft jemand!

Verwandte Themen