Создание строковых строк С (не распечатывать их)

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

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

Любой способ сократить это?

Ответ 1

Используйте sprintf.

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

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

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

Конечный нулевой символ автоматически добавляется после содержание.

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

Параметры:

str

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

format

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

... (additional arguments)

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

Пример:

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

Ответ 2

Мне кажется, что вы хотите легко передать строку, созданную с использованием форматирования в стиле 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() от Хольгера Вайса.

Ответ 3

Если у вас есть совместимая с 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");
    }
}

Ответ 4

Если у вас есть код 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(), завершающий нуль или нет.

Ответ 5

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

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

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

Ответ 6

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, где вам нужно предоставить соответствующий буфер для печати. ​​