2016-06-21 10 views
3

Um formatierte Debugausgabe auszugeben, habe ich einen Wrapper für vsfprint geschrieben. Jetzt wollte ich genau genug Speicher für den Ausgabepuffer reservieren, anstatt nur eine zufällige hohe Puffergröße zu beanspruchen (es ist eine kleine eingebettete Plattform (ESP8266)). Dazu iteriere ich die Variablenargumente, bis ein NULL gefunden wird.C variadic wrapper

Das funktioniert gut, vorausgesetzt, dass ich nicht vergessen, einen (char *)NULL Parameter zu jedem Anruf hinzuzufügen. So, dachte ich, lassen Sie einen anderen Wrapper erstellen, eine Funktion, die nur alle Argumente weiterleitet und fügt eine (char *) NULL Parameter:

#include <stdarg.h> 
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> // malloc 

void write_log(const char *format, ...) { 

    char* buffdyn; 
    va_list args; 

    // CALC. MEMORY 
    size_t len; 
    char *p; 

    if(format == NULL) 
    return; 

    len = strlen(format); 

    va_start(args, format); 

    while((p = va_arg(args, char *)) != NULL) 
    len += strlen(p); 

    va_end(args); 
    // END CALC. MEMORY 

    // ALLOCATE MEMORY 
    buffdyn = malloc(len + 1); /* +1 for trailing \0 */ 
    if(buffdyn == NULL) { 
    printf("Not enough memory to process message."); 
    return; 
    } 

    va_start(args, format); 
    //vsnprintf = Write formatted data from variable argument list to sized buffer 
    vsnprintf(buffdyn, len, format, args); 
    va_end(args); 

    printf("%s\r\n",buffdyn); 
    free(buffdyn); 
} 

void write_log_wrapper(const char *format, ...) { 

    va_list arg; 

    va_start(arg, format); 
    write_log(format,arg,(char *)NULL); 
    va_end(arg); 
} 


int main() 
{ 
    const char* sDeviceName = "TEST123"; 
    const char* sFiller1 = "12345678"; 

    write_log_wrapper("Welcome to %s%s", sDeviceName,sFiller1); 
    write_log("Welcome to %s%s", sDeviceName,sFiller1, (char *)NULL); 

    return 0; 
} 

Aufruf der write_log() Funktion direkt einwandfrei funktioniert (wenn Sie nicht den NULL-Parameter vergessen). Rufen Sie die write_log_wrapper() Funktion wird nur den ersten Parameter angezeigt, und fügt dann ein "(nu" (Müll?) Zum Ausgang.

Was mache ich falsch? Ist dies ein guter Weg, um zu nähern, was ich anstrebe tun in erster Linie?

Dank.

+2

Wenn Sie vorhaben, es auf 'stdout' zu schreiben, warum benutzen Sie nicht einfach' vprintf'? Dasselbe gilt, wenn Sie in eine Datei schreiben wollen (benutzen Sie 'vfprintf'). Dann gibt es überhaupt keine Pufferzuweisung in Ihrem Code und Sie müssen nicht herausfinden, wie viele Argumente oder wie groß ein Puffer ist. –

+3

Sie ignorieren die Möglichkeit, dass eines der übergebenen Argumente nicht eine NUL-terminierte Zeichenfolge ist, die über ein 'char *' übergeben wird. Was ist, wenn Sie ein "int" oder ein "double" bekommen? –

+0

... oder ein NULL mit% p –

Antwort

2

Um zu bestimmen, wie groß ein Puffer, um die Ausgabezeichenfolge zu halten, benötigt wird, müssen Sie das gesamte Format-String vollständig analysieren und die Argumente tatsächlich erweitern.

Sie können entweder tun es sich, die gesamte Verarbeitung von printf() und seinesgleichen zu duplizieren und zu hoffen, irgendwelche Fehler nicht zu machen, oder Sie können vsnprintf() verwenden - zunächst die Größe, um zu bestimmen, und dann zu erweitern tatsächlich die Eingänge auf einen Ausgang Zeichenfolge.

#define FIXED_SIZE 64 

void write_log(const char *format, ...) 
{ 
    // set up a fixed-size buffer and a pointer to it 
    char fixedSizeBuffer[ FIXED_SIZE ]; 
    char *outputBuffer = fixedSizeBuffer; 

    // no dynamic buffer yet 
    char *dynamicBuffer = NULL; 

    // get the variable args 
    va_list args1; 
    va_start(args1, format); 

    // need to copy the args even though we won't know if we 
    // need them until after we use the first set 
    va_list args2; 
    va_copy(args2, args1); 

    // have to call vsnprintf at least once - might as well use a small 
    // fixed-size buffer just in case the final string fits in it 
    int len = vsnprintf(fixedSizeBuffer, sizeof(fixedSizeBuffer), format, args1); 
    va_end(args1); 

    // it didn't fit - get a dynamic buffer, expand the string, and 
    // point the outputBuffer pointer at the dynamic buffer so later 
    // processing uses the right string 
    if (len > sizeof(fixedSizeBuffer )) 
    { 
     dynamicBuffer = malloc(len + 1); 
     vsnprintf(dynamicBuffer, len + 1, format, args2); 
     outputBuffer = dynamicBuffer; 
    } 

    va_end(args2); 

    // do something with outputBuffer 

    free(dynamicBuffer); 
    return; 
} 
+2

gute Idee, aber es gibt Systeme, in denen man 'Argumente' nicht zweimal benutzen sollte (zB Linux auf IBM Z-Serie), also sollte man einen' va_copy() ' –

+0

@IngoLeonhardt Good Punkt hinzufügen . Ich werde update –

+0

Eine sehr solide Lösung, getestet und es funktioniert aus der Box: D. – svenema

1

Was mache ich falsch?

einen Pass va_list arg

Sie werden nicht um bekommen, um eine Sentinel-Pass markiert das Ende der übergebenen Parameter
write_log(format, arg, (char *)NULL); 

ist nicht das gleiche wie


mehrere char*

write_log("Welcome to %s%s", sDeviceName, sFiller1, (char *)NULL); 
vorbei, dass ein (char*) NULL ist oder was auch immer Sie sich entscheiden, zu verwenden, .


Alternativen wären

  • die Anzahl der Argumente explizit übergeben, vielleicht als 2. Parameter
  • das Format-String für Konvertierungsspezifizierer analysieren, in der Tat imitiert, was printf tut.
1

Wenn Sie nur sicherstellen wollen, dass alle Anrufe ein setinel am Ende erhalten, verwenden Sie einen Makro:

#define WRITE_LOG(...) write_log(__VA_ARGS__, (char*)0) 

Dies stellt sicher, dass es immer ein extra 0 am Ende ist.

Seien Sie auch vorsichtig mit NULL. Es ist im C-Standard unterspezifiziert, welche Ausdrücke dies löst. Häufige Fälle sind 0 und (void*)0. Auf 64-Bit-Architekturen können diese also unterschiedlich breit sein (32 Bit für das erste, 64 Bit für das zweite). Es kann für eine variable Funktion tödlich sein, hier die falsche Breite zu erhalten. Daher habe ich (char*)0 verwendet, was der Typ ist, den Ihre Funktion zu erwarten scheint. (Aber (void*)0 würde auch in diesem speziellen Fall tun.)

+0

Angesichts der Tatsache, dass die Zielplattform "eine kleine eingebettete Plattform" ist, würde ich dem OP empfehlen, zu überprüfen, dass '(char *) 0' eigentlich ein' NULL'-Zeiger für den verwendeten Compiler ist. Ich glaube nicht, dass '(char *) 0 'bis C11 als gültiger' NULL'-Zeiger definiert wurde. –

+0

@AndrewHenle, meinen Sie wahrscheinlich Nullzeiger und nicht 'NULL' Zeiger. '(char *) 0' ist keine" Nullzeigerkonstante "im Sinne des Standards, hier würde man eigentlich' (void *) 0' brauchen. Für den vorliegenden Fall ist dies jedoch irrelevant, es ist ein Nullzeiger des richtigen Typs, wie das Programm erwartet. –

+0

Es funktioniert manchmal, manchmal stürzt .. scheint von der Anzahl der Argumente abhängen .. Kann nicht wirklich verstehen, warum noch. – svenema