2010-09-22 13 views
28

Estoy buscando una implementación similar a sprintf() de una función que asigna automáticamente la memoria requerida. Así que quiero decirsprintf() con asignación de memoria automática?

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

y my_str recupera la dirección de memoria asignada que contiene el resultado de esta sprintf().

En otro foro, leí que esto puede resolverse de esta manera:

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

Pero esto immediatelly resulta en una violación de segmento cuando se invoca el sprintf() con el NULL-puntero.

¿Alguna idea, solución o sugerencia? Una pequeña implementación de un analizador tipo sprintf() que se coloca en el dominio público ya sería suficiente, entonces podría hacerlo yo mismo.

¡Muchas gracias!

+1

Quien sea que le haya dado ese consejo probablemente signifique que debe usar 'snprintf', no' sprintf'. –

Respuesta

10
  1. Si es posible, use snprintf - proporciona una manera fácil de medir el tamaño de los datos que se producirían para que pueda asignar espacio.
  2. Si realmente no se puede hacer eso, otra posibilidad es imprimir en un archivo temporal con fprintf para obtener el tamaño, asignar la memoria y luego usar sprintf. snprintf es definitivamente aunque el método preferido.
23

GNU y BSD tienen asprintf y vasprintf que están diseñados para hacer justamente eso para usted. Se dará cuenta de cómo asignar la memoria para usted y devolverá nulo en cualquier error de asignación de memoria.

asprintf hace lo correcto con respecto a la asignación de cadenas: primero mide el tamaño, luego intenta asignar con malloc. En su defecto, devuelve nulo. A menos que tenga su propio sistema de asignación de memoria que impida el uso de malloc, asprintf es la mejor herramienta para el trabajo.

El código se vería así:

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

asprintf() sería la función de mi elección, pero desafortunadamente, no es estándar y no es portátil, ¡mala! –

+1

@ the-shamen - lo que está pidiendo es, por definición, no estándar y no portátil. Obtenga la fuente para 'asprintf' y acceda a su proyecto si lo necesita, o vuelva a implementarlo de manera independiente. – bstpierre

+11

No he oído hablar de un 'asprintf()' que devuelve un puntero. El que viene con GNU y BSD (y provisto por gnulib y libstrl) tiene el mismo valor de retorno que la llamada 'printf()' equivalente y toma un puntero a un puntero como primer argumento. Entonces, 'char * s; int ret = asprintf (& s, "% s% d% s!", a, c, b); 'con error en' ret == -1'. Me pregunto, ¿qué sistemas/bibliotecas proporcionan un 'asprintf()' que devuelve un puntero como en esta respuesta? – binki

4

La biblioteca GLib proporciona una función g_strdup_printf que hace exactamente lo que quiere, si el ligado con GLib es una opción. A partir de la documentación:

Al igual que en el estándar de C sprintf() función, pero más seguro, ya que calcula el espacio máximo necesario y asigna memoria para contener el resultado . La cadena devuelta debe ser liberada con g_free() cuando ya no sea necesario .

+0

¡Hola, gracias! Pero esto es solo glibc, necesito una solución independiente de la plataforma. Entonces, ¿es mejor hacer esto yo solo? –

+3

GLib (la base de GTK +), no GNU C Library (glibc). Pero es equivalente a asprintf de glibc. –

+0

glib es platform independant – Julius

23

Aquí está la respuesta original from Stack Overflow. Como han mencionado otros, necesita snprintf, no sprintf. Asegúrese de que el segundo argumento para snprintf es zero. Eso evitará que snprintf escriba en la cadena NULL que es el primer argumento.

El segundo argumento es necesario porque indica a snprintf que no hay espacio suficiente para escribir en el búfer de salida. Cuando no hay suficiente espacio disponible snprintf devuelve el número de bytes que debería haber escrito, si hubiera suficiente espacio disponible.

Reproducir el código de ese enlace aquí ...

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); 
    snprintf(buffer, needed, "%s: %s (%d)", msg, strerror(errno), errno); 
    return buffer; 
} 
+3

¿No debería agregar 1 a 'needed' para dar cuenta del carácter nulo de terminación? – beldaz

+2

No detectó el +1 al final de la primera línea al principio (estaba fuera del área visible): 'size_t needed = snprintf (...) + 1;' – user2421739

9

Si se puede vivir con extensiones GNU/BSD, la pregunta ya está contestada. Puede usar asprintf() (y vasprintf() para crear funciones de envoltura) y listo.

Pero snprintf() y vsnprintf() tienen el mandato por POSIX, de acuerdo con la página de manual, y el último puede ser utilizado para construir su propia versión simple de asprintf() y 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; 
} 

Usted puede hacer un poco de magia preprocesador y utilizar sus versiones de funciones sólo en sistemas que no soportan ellos.

+1

Solo puede pasar la variable 'va_list' a una función Para usar 'vsnprintf()' dos veces como lo hace en 'vasprintf()' debe usar 'va_copy()'. –

+0

@IliaK. ¿Es correcto, ahora? –

+1

Será si agrega 'va_end (ap1)' antes de regresar de 'vasprintf()' (por ejemplo, justo después de una llamada a 'vsnprintf()'). –

Cuestiones relacionadas