web-dev-qa-db-fra.com

sprintf () avec allocation automatique de mémoire?

Je recherche une implémentation de type sprintf () d'une fonction qui alloue automatiquement la mémoire requise. Je veux donc dire

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

et my_str récupère l'adresse d'une mémoire allouée qui contient le résultat de ce sprintf ().

Dans un autre forum, j'ai lu que cela peut être résolu comme ceci:

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

Mais cela entraîne immédiatement une erreur de segmentation lorsque le sprintf () avec le pointeur NULL est invoqué.

Vous avez donc une idée, une solution ou des conseils? Une petite implémentation d'un analyseur similaire à sprintf () qui est placé dans le domaine public serait déjà suffisante, alors je pourrais le faire moi-même.

Merci beaucoup!

36
the-shamen

Voici la réponse originale de Stack Overflow . Comme d'autres l'ont mentionné, vous avez besoin de snprintf et non sprintf. Assurez-vous que le deuxième argument de snprintf est zero. Cela empêchera snprintf d'écrire dans la chaîne NULL qui est le premier argument.

Le deuxième argument est nécessaire car il indique à snprintf que suffisamment d'espace n'est pas disponible pour écrire dans le tampon de sortie. Lorsque suffisamment d'espace n'est pas disponible, snprintf renvoie le nombre d'octets qu'il aurait écrit si suffisamment d'espace avait été disponible.

Reproduire le code de ce lien ici ...

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;
}
38
Tarun

GNU et BSD ont asprintf et vasprintf qui sont conçus pour faire exactement cela pour vous. Il découvrira comment allouer la mémoire pour vous et renverra null en cas d'erreur d'allocation de mémoire.

asprintf fait la bonne chose en ce qui concerne l'allocation de chaînes - il mesure d'abord la taille, puis il essaie d'allouer avec malloc. A défaut, il renvoie null. À moins que vous n'ayez votre propre système d'allocation de mémoire qui empêche l'utilisation de malloc, asprintf est le meilleur outil pour le travail.

Le code ressemblerait à:

#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;
}
25
Mike Axiak

Si vous pouvez vivre avec des extensions GNU/BSD, la question est déjà répondue. Vous pouvez utiliser asprintf() (et vasprintf() pour construire des fonctions wrapper) et c'est fait.

Mais snprintf() et vsnprintf() sont mandatés par POSIX, selon la page de manuel, et cette dernière peut être utilisée pour créer votre propre version simple de asprintf() et 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;
}

Vous pouvez faire de la magie de préprocesseur et utiliser vos versions de fonctions uniquement sur les systèmes qui ne les prennent pas en charge.

13
Pavel Šimerda
  1. Si possible, utilisez snprintf - cela donne un moyen facile de mesurer la taille des données qui seraient produites afin que vous puissiez allouer de l'espace.
  2. Si vous vraiment ne pouvez pas faire cela, une autre possibilité est d'imprimer dans un fichier temporaire avec fprintf pour obtenir la taille, allouez la mémoire , puis utilisez sprintf. snprintf is certainement la méthode préférée cependant.
10
Jerry Coffin

La bibliothèque GLib fournit une fonction g_strdup_printf qui fait exactement ce que vous voulez, si la liaison avec GLib est une option. De la documentation:

Similaire à la fonction C sprintf() standard mais plus sûre, car elle calcule l'espace maximum requis et alloue de la mémoire pour contenir le résultat. La chaîne retournée doit être libérée avec g_free() lorsqu'elle n'est plus nécessaire.

5
Paul Kuliniewicz

POSIX.1 (alias IEEE 1003.1-2008) fournit open_memstream:

char *ptr;
size_t size;
FILE *f = open_memstream(&ptr, &size);
fprintf(f, "lots of stuff here\n");
fclose(f);
write(1, ptr, size); /* for example */
free(ptr);

open_memstream (3) est disponible sur au moins Linux et macOS et l'est depuis quelques années. L'inverse de open_memstream (3) est fmemopen (3) qui rend le contenu d'un tampon disponible pour la lecture.

Si vous voulez juste un seul sprintf (3), l'asprintf (3) largement implémenté mais non standard pourrait être ce que vous voulez.

0
John Haxby
/*  casprintf print to allocated or reallocated string

char *aux = NULL;
casprintf(&aux,"first line\n");
casprintf(&aux,"seconde line\n");
printf(aux);
free(aux);
*/
int vcasprintf(char **strp,const char *fmt,va_list ap)
{
  int ret;
  char *strp1;
  char *result;
  if (*strp==NULL)
     return vasprintf(strp,fmt,ap);

  ret=vasprintf(&strp1,fmt,ap); // ret = strlen(strp1) or -1
  if (ret == -1 ) return ret;
  if (ret==0) {free(strp1);return strlen(*strp);}

  size_t len = strlen(*strp);
  *strp=realloc(*strp,len + ret +1);
  memcpy((*strp)+len,strp1,ret+1);
  free(strp1);
  return(len+ret);
}

int casprintf(char **strp, const char *fmt, ...)
{
 int ret;
 va_list ap;
 va_start(ap,fmt);
 ret =vcasprintf(strp,fmt,ap);
 va_end(ap);
 return(ret);
}