2016-06-21 3 views
3

Для вывода отформатированного отладочного вывода я написал обертку для vsfprint. Теперь я хотел выделить достаточно памяти для выходного буфера вместо того, чтобы просто требовать размер случайного большого буфера (это небольшая встроенная платформа (ESP8266)). Для этого я повторяю аргументы переменной до тех пор, пока не будет найден NULL.C variadic wrapper

Это прекрасно работает при условии, что я не забудьте добавить параметр (char *)NULL к каждому звонку. Итак, я подумал, давайте создать еще одну оболочку, функцию, которая просто ретранслирует все аргументы и добавляет параметр (char *) NULL:

#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; 
} 

Вызов функции write_log() непосредственно работает нормально (если вы не забыли параметр NULL). Вызов функции write_log_wrapper() отобразит только первый параметр, а затем добавит к выходу «(nu» (мусор?)).

Что я делаю неправильно? Является ли это хорошим способом приблизиться к тому, к чему я стремлюсь делать в первую очередь?

спасибо.

+2

Если вы намереваетесь написать его на 'stdout', почему бы вам просто не использовать' vprintf'? То же самое следует, если вы хотите записать в файл (используйте 'vfprintf'). Тогда в вашем коде вообще нет выделения буфера, и вам не нужно разбираться в том, сколько аргументов или сколько буфера. –

+3

Вы игнорируете возможность того, что один из переданных аргументов не является строкой, завершенной NUL, переданной через 'char *'. Что делать, если вы получаете 'int' или' double'? –

+0

... или NULL с% p –

ответ

2

Чтобы определить, насколько большой буфер необходим для хранения выходной строки, вам необходимо полностью разобрать всю строку формата и фактически расширить аргументы.

Вы можете сделать это самостоятельно, дублируя все обработку printf() и его иже с ними и в надежде не делать какие-либо ошибки, или вы можете использовать vsnprintf() - первый, чтобы определить размер, а затем на самом деле расширения входов на один выход строка.

#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

, но есть системы, в которых вы не должны использовать 'args' дважды (например, linux для серии IBM Z), поэтому вы должны добавить' va_copy() ' –

+0

@IngoLeonhardt Хорошая точка , Я обновлю –

+0

Очень прочное решение, протестировав его, и оно работает из коробки: D. – svenema

1

Что я делаю неправильно?

Передача va_list arg

write_log(format, arg, (char *)NULL); 

не то же самое, проходя несколько char*

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

Вы не получите вокруг, чтобы пройти часовую маркировку конца параметров, переданных, то есть (char*) NULL или все, что вы решили использовать ,


Альтернативы будет

  • передать число аргументов явно, возможно, второй параметр
  • разобрать строку формата для преобразования спецификаторов, фактически имитируя, что printf делает.
1

Если вы хотите просто чтобы убедиться, что все звонки получают setinel в конце концов, использовать макрос:

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

Это гарантирует, что всегда есть дополнительный 0 в конце.

Также будьте осторожны с NULL. Он не указан в стандарте C, к каким выражениям это разрешается. Обычными случаями являются 0 и (void*)0. Таким образом, на 64-битных архитектурах они могут иметь разную ширину (32 бит для первого, 64 бит для второго). Это может быть смертельным для переменной функции, чтобы получить неправильную ширину здесь. Поэтому я использовал (char*)0, который, как кажется, ожидает вас. (Но (void*)0 также будет делать в этом специальном случае.)

+0

Учитывая, что целевая платформа представляет собой «небольшую встроенную платформу», я бы рекомендовал OP убедиться, что '(char *) 0' фактически является указателем' NULL' для используемого компилятора перед использованием в качестве такового. Я не думаю, что '(char *) 0' было обязано быть допустимым указателем« NULL »до C11. –

+0

@AndrewHenle, вы, вероятно, имеете в виду нулевой указатель, а не указатель 'NULL'. '(char *) 0' не является« нулевой константой указателя »в смысле стандарта, здесь вам действительно понадобится' (void *) 0'. Но для рассматриваемого случая это не имеет значения, это нулевой указатель нужного типа, который ожидает программа. –

+0

Иногда это срабатывает, иногда сбой .. похоже, зависит от количества аргументов. Пока не могу понять, почему. – svenema