sprintf () с автоматическим распределением памяти? - PullRequest
35 голосов
/ 23 сентября 2010

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

char* my_str = dynamic_sprintf( "Hello %s, this is a %.*s nice %05d string", a, b, c, d );

и my_str извлекает адрес выделенной памяти, которая содержит результат этого sprintf ().

На другом форуме я читал, что это можно решить так:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char*   ret;
    char*   a = "Hello";
    char*   b = "World";
    int     c = 123;

    int     numbytes;

    numbytes = sprintf( (char*)NULL, "%s %d %s!", a, c, b );
    printf( "numbytes = %d", numbytes );

    ret = (char*)malloc( ( numbytes + 1 ) * sizeof( char ) );
    sprintf( ret, "%s %d %s!", a, c, b );

    printf( "ret = >%s<\n", ret );
    free( ret );

    return 0;
}

Но это немедленно приводит к segfault, когда вызывается sprintf () с NULL-указателем.

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

Большое спасибо!

Ответы [ 5 ]

33 голосов
/ 30 апреля 2012

Вот оригинальный ответ из Переполнения стека . Как уже упоминали другие, вам нужно snprintf не sprintf. Убедитесь, что второй аргумент snprintf равен zero. Это предотвратит запись snprintf в строку NULL, которая является первым аргументом.

Второй аргумент необходим, потому что он сообщает snprintf, что недостаточно места для записи в выходной буфер. Если свободного места недостаточно, snprintf возвращает количество записанных байт, если бы было достаточно места.

Воспроизведение кода по этой ссылке здесь ...

char* get_error_message(char const *msg) {
    size_t needed = snprintf(NULL, 0, "%s: %s (%d)", msg, strerror(errno), errno) + 1;
    char  *buffer = malloc(needed);
    sprintf(buffer, "%s: %s (%d)", msg, strerror(errno), errno);
    return buffer;
}
25 голосов
/ 23 сентября 2010

GNU и BSD имеют asprintf и vasprintf, которые предназначены именно для этого. Он выяснит, как выделить память для вас, и при любой ошибке выделения памяти вернет null.

asprintf делает правильные вещи в отношении выделения строк - сначала он измеряет размер, затем пытается выделить с помощью malloc. В противном случае он возвращает ноль. Если у вас нет собственной системы выделения памяти, которая исключает использование malloc, asprintf - лучший инструмент для этой работы.

Код будет выглядеть так:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char*   ret;
    char*   a = "Hello";
    char*   b = "World";
    int     c = 123;

    ret = asprintf( "%s %d %s!", a, c, b );
    if (ret == NULL) {
        fprintf(stderr, "Error in asprintf\n");
        return 1;
    }

    printf( "ret = >%s<\n", ret );
    free( ret );

    return 0;
}
13 голосов
/ 16 октября 2013

Если вы можете жить с расширениями GNU / BSD, вопрос уже получен.Вы можете использовать asprintf()vasprintf() для построения функций оболочки) и все готово.

Но snprintf() и vsnprintf() предписаны POSIX, в соответствии с man-страницей, и последняя может использоватьсясоздать собственную простую версию asprintf() и vasprintf().

int
vasprintf(char **strp, const char *fmt, va_list ap)
{
    va_list ap1;
    size_t size;
    char *buffer;

    va_copy(ap1, ap);
    size = vsnprintf(NULL, 0, fmt, ap1) + 1;
    va_end(ap1);
    buffer = calloc(1, size);

    if (!buffer)
        return -1;

    *strp = buffer;

    return vsnprintf(buffer, size, fmt, ap);
}

int
asprintf(char **strp, const char *fmt, ...)
{
    int error;
    va_list ap;

    va_start(ap, fmt);
    error = vasprintf(strp, fmt, ap);
    va_end(ap);

    return error;
}

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

10 голосов
/ 23 сентября 2010
  1. Если возможно, используйте snprintf - это дает простой способ измерить размер данных, которые будут получены, чтобы вы могли выделить место.
  2. Если вы действительно не можете этого сделать, другой возможностью является печать во временный файл с fprintf, чтобы получить размер, выделить память и затем использовать sprintf. snprintf является определенно предпочтительным методом, хотя.
5 голосов
/ 23 сентября 2010

Библиотека GLib предоставляет функцию g_strdup_printf, которая делает именно то, что вы хотите, если опция связывания с GLib является опцией.Из документации:

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

...