Создание строк в формате C (без их печати) - PullRequest
84 голосов
/ 30 апреля 2009

У меня есть функция, которая принимает строку, то есть:

void log_out(char *);

При вызове мне нужно на лету создать отформатированную строку, например:

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

Как мне это сделать в ANSI C?


Только, поскольку sprintf() возвращает int, это означает, что мне нужно написать как минимум 3 команды, например:

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

Есть ли способ сократить это?

Ответы [ 7 ]

75 голосов
/ 30 апреля 2009

Использование sprintf .

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

Запись отформатированных данных в строку. Составляет строку с тем же текстом. это будет напечатано, если формат был использован в printf, но вместо при печати содержимое сохраняется в виде строки C в буфере указал на ул.

Размер буфера должен быть достаточно большим, чтобы вместить весь результирующая строка (см. snprintf для более безопасной версии).

Завершающий нулевой символ автоматически добавляется после содержание.

После параметра форматирования функция ожидает как минимум столько же дополнительные аргументы, необходимые для формата.

Параметры:

str

Указатель на буфер, в котором хранится полученная C-строка. Буфер должно быть достаточно большим, чтобы содержать результирующую строку.

format

C строка, которая содержит строку формата, которая следует за тем же спецификации в формате printf (подробности см. в printf).

... (additional arguments)

В зависимости от строки формата функция может ожидать последовательность дополнительные аргументы, каждый из которых содержит значение, которое будет использоваться для замены спецификатор формата в строке формата (или указатель на хранилище местоположение, для п). Там должно быть как минимум столько же аргументов как количество значений, указанных в спецификаторах формата. дополнительный аргументы игнорируются функцией.

* +1032 * Пример: * 1 033 *
// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello" "world");
11 голосов
/ 24 мая 2014

Если у вас есть система, совместимая с POSIX-2008 (любой современный Linux), вы можете использовать безопасную и удобную функцию asprintf(): она будет malloc() достаточной памяти для вас, вам не нужно беспокоиться о максимуме размер строки Используйте это так:

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

Это минимальное усилие, которое вы можете получить, чтобы создать строку безопасным способом. Код sprintf(), который вы дали в вопросе, глубоко ошибочен:

  • За указателем нет выделенной памяти. Вы записываете строку в случайное место в памяти!

  • Даже если вы написали

    char s[42];
    

    у вас будут большие проблемы, потому что вы не можете знать, какой номер поставить в скобки.

  • Даже если бы вы использовали «безопасный» вариант snprintf(), вы все равно рискуете, что ваши строки будут обрезаны. При записи в файл журнала это относительно незначительная проблема, но она потенциально способна обрезать именно ту информацию, которая была бы полезна. Кроме того, он обрежет символ конца строки, приклеивая следующую строку журнала к концу вашей неудачно написанной строки.

  • Если вы попытаетесь использовать комбинацию malloc() и snprintf() для получения правильного поведения во всех случаях, вы получите примерно вдвое больше кода, чем я дал для asprintf(), и в основном перепрограммируете функциональность asprintf().


Если вы хотите предоставить оболочку log_out(), которая может принимать список параметров в стиле printf(), вы можете использовать вариант vasprintf(), который принимает va_list в качестве аргумента. Вот совершенно безопасная реализация такой обертки:

//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");
    }
}
11 голосов
/ 30 апреля 2009

Мне кажется, что вы хотите иметь возможность легко передавать строку, созданную с использованием форматирования в стиле printf, в функцию, которая у вас уже есть, которая принимает простую строку. Вы можете создать функцию-оболочку, используя stdarg.h средства и vsnprintf() (которые могут быть недоступны в зависимости от вашего компилятора / платформы):

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

Для платформ, которые не обеспечивают хорошую реализацию (или любую реализацию) семейства подпрограмм snprintf(), я успешно использовал почти общедоступный домен snprintf() от Holger Weiss .

3 голосов
/ 30 апреля 2009

Если у вас есть код для log_out(), перепишите его. Скорее всего, вы можете сделать:

static FILE *logfp = ...;

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

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

Если требуется дополнительная информация для регистрации, которую можно распечатать до или после показанного сообщения. Это сохраняет распределение памяти и сомнительные размеры буфера и так далее и тому подобное. Возможно, вам нужно инициализировать logfp в ноль (нулевой указатель) и проверить, является ли он нулевым, и открыть файл журнала соответствующим образом - но код в существующем log_out() должен все равно иметь с этим дело.

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

Если у вас нет кода log_out(), подумайте, можете ли вы заменить его на вариант, такой как описанный выше. Возможность использования одного и того же имени будет зависеть от структуры вашего приложения и конечного источника текущей функции log_out(). Если он находится в том же объектном файле, что и другая незаменимая функция, вам придется использовать новое имя. Если вы не можете понять, как точно его воспроизвести, вам придется использовать какой-то вариант, подобный приведенному в других ответах, который выделяет соответствующий объем памяти.

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? */
}

Очевидно, что теперь вы вызываете log_out_wrapper() вместо log_out() - но выделение памяти и т. Д. Выполняется один раз. Я оставляю за собой право перераспределять пространство одним ненужным байтом - я не перепроверил, содержит ли длина, возвращаемая vsnprintf(), завершающий ноль или нет.

2 голосов
/ 31 декабря 2017

Не используйте sprintf.
Он переполнит ваш String-Buffer и приведет к краху вашей программы.
Всегда используйте snprintf

0 голосов
/ 30 апреля 2009

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html дает следующий пример для печати в stderr. Вы можете изменить его, чтобы использовать вместо этого функцию журнала:

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

Вместо vfprintf вам нужно будет использовать vsprintf, где вам нужно предоставить соответствующий буфер для печати. ​​

0 голосов
/ 30 апреля 2009

Я этого не делал, поэтому просто укажу правильный ответ.

C содержит положения для функций, которые принимают неопределенное количество операндов, используя заголовок <stdarg.h>. Вы можете определить свою функцию как void log_out(const char *fmt, ...); и получить va_list внутри функции. Затем вы можете выделить память и вызвать vsprintf() с выделенной памятью, форматом и va_list.

В качестве альтернативы, вы можете использовать это, чтобы написать функцию, аналогичную sprintf(), которая бы выделяла память и возвращала отформатированную строку, генерируя ее более или менее, как указано выше. Это будет утечка памяти, но если вы просто выходите из системы, это может не иметь значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...