Strings im C-Format erstellen (nicht drucken)

Lesezeit: 9 Minuten

Benutzeravatar von pistacchio
Pistazie

Ich habe eine Funktion, die eine Zeichenfolge akzeptiert, das heißt:

void log_out(char *);

Beim Aufruf muss ich eine formatierte Zeichenfolge wie folgt erstellen:

int i = 1;
log_out("some text %d", i);

Wie mache ich das in ANSI C?


Nur seit sprintf() gibt ein int zurück, das bedeutet, dass ich mindestens 3 Befehle schreiben muss, wie:

char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);

Kann man das irgendwie verkürzen?

  • Ich vertraue darauf, dass der Funktionsprototyp wirklich ist: extern void log_out(const char *, …); denn wenn nicht, ist der Aufruf fehlerhaft (zu viele Argumente). Es sollte einen konstanten Zeiger nehmen, da es keinen Grund für log_out() gibt, den String zu ändern. Natürlich könnten Sie sagen, dass Sie eine einzelne Zeichenfolge an die Funktion übergeben möchten – aber nicht können. Eine Möglichkeit besteht dann darin, eine varargs-Version der Funktion log_out() zu schreiben.

    – Jonathan Leffler

    29. April 2009 um 22:51 Uhr

Benutzeravatar von akappa
Akappa

Verwenden Sprintf. (Dies ist NICHT sicher, aber OP hat nach einer ANSI C-Antwort gefragt. Siehe die Kommentare für eine sichere Version.)

int sprintf ( char * str, const char * format, ... );

Formatierte Daten in String schreiben Erzeugt einen String mit dem gleichen Text, der ausgegeben würde, wenn format auf printf verwendet würde, aber anstatt gedruckt zu werden, wird der Inhalt als C-String in dem Puffer gespeichert, auf den str zeigt.

Die Größe des Puffers sollte groß genug sein, um den gesamten resultierenden String aufzunehmen (siehe snprintf für eine sicherere Version).

Nach dem Inhalt wird automatisch ein abschließendes Nullzeichen angehängt.

Nach dem format-Parameter erwartet die Funktion mindestens so viele zusätzliche Argumente wie für format benötigt werden.

Parameter:

str

Zeiger auf einen Puffer, in dem der resultierende C-String gespeichert wird. Der Puffer sollte groß genug sein, um die resultierende Zeichenfolge aufzunehmen.

format

C-Zeichenfolge, die eine Formatzeichenfolge enthält, die den gleichen Spezifikationen wie das Format in printf folgt (siehe printf für Details).

... (additional arguments)

Abhängig von der Formatzeichenfolge erwartet die Funktion möglicherweise eine Folge zusätzlicher Argumente, die jeweils einen Wert enthalten, der zum Ersetzen eines Formatbezeichners in der Formatzeichenfolge (oder eines Zeigers auf einen Speicherort für n) verwendet wird. Es sollten mindestens so viele dieser Argumente vorhanden sein wie die Anzahl der in den Formatbezeichnern angegebenen Werte. Zusätzliche Argumente werden von der Funktion ignoriert.

Beispiel:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");

  • Huch! Verwenden Sie nach Möglichkeit die ‘n’ Variationsfunktionen. Dh snprintf. Sie werden Sie dazu bringen, Ihre Puffergrößen zu zählen und sich dadurch gegen Überläufe zu versichern.

    – dmckee — Ex-Moderator-Kätzchen

    29. April 2009 um 21:40 Uhr

  • Ja, aber er hat nach einer ANSI-C-Funktion gefragt und ich bin mir nicht sicher, ob snprintf ansi oder sogar posix ist.

    – Akappa

    29. April 2009 um 21:59 Uhr

  • Ah. Das habe ich vermisst. Aber ich werde durch den neuen Standard gerettet: Die ‘n’-Varianten sind offiziell in C99. FWIW, YMMV usw.

    – dmckee — Ex-Moderator-Kätzchen

    29. April 2009 um 22:04 Uhr

  • die snprintf sind nicht der sicherste Weg. Sie sollten mit snprintf_s gehen. Sehen msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx

    – Spaß

    29. April 2009 um 23:17 Uhr

  • @Joce – die C99-Funktionsfamilie snprintf() ist ziemlich sicher; Die snprintf_s()-Familie hat jedoch ein anderes Verhalten (insbesondere in Bezug darauf, wie das Abschneiden behandelt wird). ABER – Microsofts _snprintf()-Funktion ist nicht sicher – da sie möglicherweise den resultierenden Puffer unterminiert lässt (C99 snprintf() wird immer beendet).

    – Michael Burr

    30. April 2009 um 1:08 Uhr

cmaster - Benutzeravatar von Monica wiederherstellen
cmaster – monica wieder einsetzen

Wenn Sie ein POSIX-2008-kompatibles System haben (jedes moderne Linux), können Sie das sicher und bequem verwenden asprintf() Funktion: Es wird malloc() genügend Speicher für Sie haben, müssen Sie sich keine Gedanken über die maximale Zeichenfolgengröße machen. Verwenden Sie es wie folgt:

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

Dies ist der minimale Aufwand, den Sie unternehmen können, um die Saite sicher zu konstruieren. Das sprintf() Der Code, den Sie in der Frage angegeben haben, ist zutiefst fehlerhaft:

  • Hinter dem Zeiger befindet sich kein zugewiesener Speicher. Sie schreiben die Zeichenfolge an eine zufällige Stelle im Speicher!

  • Auch wenn du geschrieben hättest

    char s[42];
    

    Sie würden in große Schwierigkeiten geraten, weil Sie nicht wissen, welche Zahl Sie in die Klammern setzen sollen.

  • Auch wenn Sie die „sichere“ Variante verwendet hätten snprintf(), würden Sie immer noch Gefahr laufen, dass Ihre Zeichenfolgen abgeschnitten werden. Beim Schreiben in eine Protokolldatei ist dies ein relativ geringes Problem, aber es kann dazu führen, dass genau die Informationen abgeschnitten werden, die nützlich gewesen wären. Außerdem wird das abschließende Endline-Zeichen abgeschnitten und die nächste Protokollzeile an das Ende Ihrer erfolglos geschriebenen Zeile geklebt.

  • Wenn Sie versuchen, eine Kombination aus zu verwenden malloc() und snprintf() Um in allen Fällen korrektes Verhalten zu erzeugen, haben Sie am Ende ungefähr doppelt so viel Code, als ich angegeben habe asprintf()und programmieren Sie im Grunde die Funktionalität von neu asprintf().


Wenn Sie einen Wrapper von log_out() das kann dauern printf() Style-Parameterliste selbst, können Sie die Variante verwenden vasprintf() was dauert va_list als Argument. Hier ist eine absolut sichere Implementierung eines solchen Wrappers:

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}

  • Beachten Sie, dass asprintf() ist weder Teil des Standards C 2011 noch Teil von POSIX, nicht einmal POSIX 2008 oder 2013. Es ist Teil von TR 27431-2: siehe Verwenden Sie die „sicheren“ Funktionen von TR 24731?

    – Jonathan Leffler

    24. Mai 2014 um 15:50 Uhr


  • wirkt wie Charme! Ich war mit dem Problem konfrontiert, als der Wert “char*” nicht richtig in Protokollen gedruckt wurde, dh die formatierte Zeichenfolge war irgendwie nicht angemessen. Code verwendete “asprintf()”.

    – Parasisch

    9. Januar 2016 um 19:38 Uhr

Benutzeravatar von Michael Burr
Michael Burr

Es klingt für mich so, als ob Sie in der Lage sein möchten, eine Zeichenfolge, die mit der Formatierung im Printf-Stil erstellt wurde, einfach an die Funktion zu übergeben, die Sie bereits haben und die eine einfache Zeichenfolge akzeptiert. Sie können eine Wrapper-Funktion mit erstellen stdarg.h Einrichtungen u vsnprintf() (die je nach Compiler/Plattform möglicherweise nicht ohne Weiteres verfügbar sind):

#include <stdarg.h>
#include <stdio.h>

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

Für Plattformen, die keine gute Implementierung (oder irgendeine Implementierung) von bieten snprintf() Familie von Routinen, die ich erfolgreich eingesetzt habe eine fast öffentliche Domäne snprintf() von Holger Weiss.

  • Derzeit könnte man die Verwendung von vsnprintf_s in Betracht ziehen.

    – verschmelzen

    3. September 2013 um 16:10 Uhr

Toms Benutzeravatar
Tom

Verwenden Sie nicht sprintf.
Es wird Ihren String-Buffer überlaufen lassen und Ihr Programm zum Absturz bringen.
Verwenden Sie immer snprintf

Wenn Sie den Code dazu haben log_out(), umschreiben. Höchstwahrscheinlich können Sie Folgendes tun:

static FILE *logfp = ...;

void log_out(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

Wenn zusätzliche Protokollinformationen benötigt werden, können diese vor oder nach der angezeigten Meldung gedruckt werden. Das spart Speicherallokation und dubiose Puffergrößen und so weiter und so fort. Sie müssen wahrscheinlich initialisieren logfp auf null (Nullzeiger) und prüfen ob dieser null ist und ggf. die Logdatei öffnen – aber den Code in der vorhandenen log_out() sollte sich trotzdem damit auseinandersetzen.

Der Vorteil dieser Lösung besteht darin, dass Sie sie einfach so aufrufen können, als wäre sie eine Variante von printf(); in der Tat ist es eine kleinere Variante auf printf().

Wenn Sie den Code nicht haben log_out(), überlegen Sie, ob Sie es durch eine Variante wie die oben beschriebene ersetzen können. Ob Sie denselben Namen verwenden können, hängt von Ihrem Anwendungsframework und der endgültigen Stromquelle ab log_out() Funktion. Wenn es sich in derselben Objektdatei wie eine andere unverzichtbare Funktion befindet, müssen Sie einen neuen Namen verwenden. Wenn Sie nicht herausfinden können, wie Sie es genau replizieren können, müssen Sie eine Variante wie die in anderen Antworten angegebenen verwenden, die eine angemessene Menge an Speicher zuweist.

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

Offensichtlich rufen Sie jetzt die an log_out_wrapper() Anstatt von log_out() – aber die Speicherzuweisung und so weiter wird einmal gemacht. Ich behalte mir das Recht vor, Speicherplatz um ein unnötiges Byte zu überbelegen – ich habe nicht überprüft, ob die zurückgegebene Länge von vsnprintf() schließt die abschließende Null ein oder nicht.

Benutzeravatar von Xiddoc
Xiddoc

Verifiziert und Zusammenfassung:

sprintf gegen asprintf

asprintf = malloc + sprintf

Beispielcode

Sprintf

int largeEnoughBufferLen = 20;

char *someStr = (char*)malloc(largeEnoughBufferLen * sizeof(char));

sprintf(someStr, "formatted string: %s %s!", "Hello", "world");

// do what you want for formatted string: someStr

free(someStr);

asprintf

char *someStr;

int formattedStrResult = asprintf(&someStr, "formatted string: %s %s!", "Hello", "world");

if(formattedStrResult > 0){
    // do what you want for formatted string: someStr

    free(someStr);
} else {
    // some error
}

Benutzeravatar von David Thornley
David Thornley

Ich habe das nicht getan, also werde ich nur auf die richtige Antwort zeigen.

C verfügt über Vorkehrungen für Funktionen, die eine unbestimmte Anzahl von Operanden verwenden, indem sie die verwenden <stdarg.h> Header. Sie können Ihre Funktion als definieren void log_out(const char *fmt, ...);und erhalten Sie die va_list innerhalb der Funktion. Dann können Sie Speicher zuweisen und aufrufen vsprintf() mit dem zugewiesenen Speicher, Format und va_list.

Alternativ könnten Sie dies verwenden, um eine Funktion analog zu zu schreiben sprintf() Das würde Speicher zuweisen und die formatierte Zeichenfolge zurückgeben, wodurch sie mehr oder weniger wie oben generiert wird. Es wäre ein Speicherleck, aber wenn Sie sich nur abmelden, spielt es möglicherweise keine Rolle.

1423630cookie-checkStrings im C-Format erstellen (nicht drucken)

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

Privacy policy