Erstellen von C-formatierten Zeichenfolgen (nicht drucken)

100

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

void log_out(char *);

Wenn ich es aufrufe, muss ich eine formatierte Zeichenfolge im laufenden Betrieb erstellen, wie:

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

Wie mache ich das in ANSI C?


Nur, da sprintf()ein int zurückgegeben wird, bedeutet dies, dass ich mindestens 3 Befehle schreiben muss, wie:

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

Wie kann man das verkürzen?

Pistacchio
quelle
1
Ich vertraue darauf, dass der Funktionsprototyp wirklich ist: extern void log_out (const char *, ...); Wenn dies nicht der Fall ist, ist der Aufruf fehlerhaft (zu viele Argumente). Es sollte ein const-Zeiger verwendet werden, da log_out () keinen Grund hat, die Zeichenfolge zu ändern. Natürlich könnten Sie sagen, dass Sie eine einzelne Zeichenfolge an die Funktion übergeben möchten - können dies aber nicht. Eine Möglichkeit besteht darin, eine varargs-Version der Funktion log_out () zu schreiben.
Jonathan Leffler

Antworten:

90

Verwenden Sie sprintf .

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

Schreiben formatierter Daten in eine Zeichenfolge Erstellt eine Zeichenfolge mit demselben Text, der gedruckt würde, wenn das Format für printf verwendet würde. Der Inhalt wird jedoch nicht gedruckt, sondern als C-Zeichenfolge in dem Puffer gespeichert, auf den str zeigt.

Die Größe des Puffers sollte groß genug sein, um die gesamte resultierende Zeichenfolge aufzunehmen (eine sicherere Version finden Sie unter snprintf).

Ein abschließendes Nullzeichen wird automatisch nach dem Inhalt angehängt.

Nach dem Formatparameter erwartet die Funktion mindestens so viele zusätzliche Argumente, wie für das Format erforderlich sind.

Parameter:

str

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

format

C-Zeichenfolge, die eine Formatzeichenfolge enthält, die denselben Spezifikationen wie das Format in printf entspricht (Einzelheiten siehe printf).

... (additional arguments)

Abhängig von der Formatzeichenfolge kann die Funktion eine Folge zusätzlicher Argumente erwarten, die jeweils einen Wert enthalten, der verwendet wird, um einen Formatbezeichner in der Formatzeichenfolge (oder einen Zeiger auf einen Speicherort für n) zu ersetzen. 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");
Akappa
quelle
34
Huch! Verwenden Sie nach Möglichkeit die Variationsfunktionen 'n'. Dh snprintf. Sie werden Sie dazu bringen, Ihre Puffergrößen zu zählen und sich so gegen Überläufe zu versichern.
dmckee --- Ex-Moderator Kätzchen
7
Ja, aber er hat nach einer ANSI C-Funktion gefragt und ich bin mir nicht sicher, ob snprintf ansi oder sogar posix ist.
Akappa
7
Ah. Das habe ich vermisst. Aber ich werde durch den neuen Standard gerettet: Die 'n'-Varianten sind in C99 offiziell. FWIW, YMMV usw.
dmckee --- Ex-Moderator Kätzchen
1
Der Snprintf ist nicht der sicherste Weg. Sie sollten mit snprintf_s gehen. Siehe msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx
joce
2
@Joce - Die Funktionsfamilie der C99 snprintf () ist ziemlich sicher. Die Familie snprintf_s () hat jedoch ein anderes Verhalten (insbesondere in Bezug auf den Umgang mit Funktionen). ABER - Die _snprintf () -Funktion von Microsoft ist nicht sicher, da der resultierende Puffer möglicherweise nicht beendet wird (C99 snprintf () wird immer beendet).
Michael Burr
15

Wenn Sie ein POSIX-2008-kompatibles System haben (jedes moderne Linux), können Sie die sichere und bequeme asprintf()Funktion verwenden: Es verfügt über malloc()genügend Speicherplatz, Sie müssen sich keine Gedanken über die maximale Zeichenfolgengröße machen. Verwenden Sie es so:

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 benötigen, um die Zeichenfolge auf sichere Weise zu erstellen. Der sprintf()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 hast

    char s[42];

    Sie wären in großen Schwierigkeiten, weil Sie nicht wissen, welche Nummer Sie in die Klammern setzen sollen.

  • Selbst wenn Sie die "sichere" Variante verwendet snprintf()hätten, besteht die Gefahr, dass Ihre Zeichenfolgen abgeschnitten werden. Beim Schreiben in eine Protokolldatei ist dies ein relativ geringes Problem, es kann jedoch genau die Informationen abgeschnitten werden, die nützlich gewesen wären. Außerdem wird das nachfolgende Endzeilenzeichen abgeschnitten und die nächste Protokollzeile an das Ende Ihrer erfolglos geschriebenen Zeile geklebt.

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


Wenn Sie auf der Bereitstellung eine Hülle aus suchen , log_out()dass kann eine Take - printf()Stil Parameterliste selbst, können Sie die Variante verwenden , vasprintf()die eine nimmt va_listals 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");
    }
}
cmaster - Monica wieder einsetzen
quelle
2
Beachten Sie, dass dies asprintf()weder Teil von Standard C 2011 noch Teil von POSIX ist, auch nicht von POSIX 2008 oder 2013. Es ist Teil von TR 27431-2: siehe Verwenden Sie die sicheren Funktionen
Jonathan Leffler
funktioniert wie Charme! Ich hatte das Problem, als der Wert "char *" nicht richtig in Protokollen gedruckt wurde, dh eine formatierte Zeichenfolge war irgendwie nicht angemessen. Code verwendete "asprintf ()".
Parasrish
11

Für mich klingt es so, als ob Sie in der Lage sein möchten, eine Zeichenfolge, die mit printf-Format erstellt wurde, einfach an die Funktion zu übergeben, die Sie bereits haben und die eine einfache Zeichenfolge benötigt. Sie können eine Wrapper-Funktion mithilfe von Funktionen erstellen stdarg.hund vsnprintf()(die je nach Compiler / Plattform möglicherweise nicht sofort 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 Implementierung) der snprintf()Routinenfamilie bieten , habe ich erfolgreich eine nahezu öffentliche Domain snprintf()von Holger Weiss verwendet .

Michael Burr
quelle
Derzeit könnte man die Verwendung von vsnprintf_s in Betracht ziehen.
Amalgamate
3

Wenn Sie den Code dazu haben log_out(), schreiben Sie ihn neu. 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 Protokollierungsinformationen erforderlich sind, können diese vor oder nach der angezeigten Nachricht gedruckt werden. Dies spart Speicherzuordnung und zweifelhafte Puffergrößen und so weiter und so fort. Sie müssen wahrscheinlich logfpauf Null initialisieren (Nullzeiger) und prüfen, ob es Null ist, und die Protokolldatei entsprechend öffnen - aber der Code in der vorhandenen log_out()sollte sich trotzdem darum kümmern .

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

Wenn Sie nicht über den Code verfügen log_out(), prüfen Sie, ob Sie ihn 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 Quelle der aktuellen log_out()Funktion ab. Wenn es sich in derselben Objektdatei befindet wie eine andere unverzichtbare Funktion, müssten Sie einen neuen Namen verwenden. Wenn Sie nicht herausfinden können, wie Sie es genau replizieren können, müssen Sie eine Variante verwenden, wie sie in anderen Antworten angegeben ist, 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? */
}

Natürlich rufen Sie jetzt das log_out_wrapper()anstelle von auf log_out()- aber die Speicherzuweisung usw. erfolgt einmal. Ich behalte mir das Recht vor, Speicherplatz um ein unnötiges Byte zu überschreiben. Ich habe nicht überprüft, ob die von zurückgegebene Länge vsnprintf()die abschließende Null enthält oder nicht.

Jonathan Leffler
quelle
2

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

Tom
quelle
0

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

C enthält Bestimmungen für Funktionen, die mithilfe des <stdarg.h>Headers eine nicht angegebene Anzahl von Operanden annehmen . Sie können Ihre Funktion als definieren void log_out(const char *fmt, ...);und das va_listInnere der Funktion abrufen. Dann können Sie Speicher zuweisen und vsprintf()mit dem zugewiesenen Speicher, Format und aufrufen va_list.

Alternativ können Sie damit eine analoge Funktion schreiben sprintf(), die Speicher zuweist und die formatierte Zeichenfolge zurückgibt und sie mehr oder weniger wie oben generiert. Es wäre ein Speicherverlust, aber wenn Sie sich nur abmelden, spielt dies möglicherweise keine Rolle.

David Thornley
quelle
-2

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html enthält das folgende Beispiel zum Drucken auf stderr. Sie können es ändern, um stattdessen Ihre Protokollfunktion zu verwenden:

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

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

Anstelle von vfprintf müssen Sie vsprintf verwenden, wo Sie einen geeigneten Puffer zum Drucken bereitstellen müssen.

Lothar
quelle