149 votes

Créer des chaînes de caractères au format C (et non les imprimer)

J'ai une fonction qui accepte une chaîne de caractères, à savoir :

void log_out(char *);

En l'appelant, j'ai besoin de créer une chaîne formatée à la volée comme :

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

Comment puis-je faire cela en ANSI C ?


Seulement, puisque sprintf() renvoie un int, cela signifie que je dois écrire au moins 3 commandes, comme :

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

Y a-t-il un moyen de raccourcir cela ?

1 votes

Je suis sûr que le prototype de la fonction est bien : extern void log_out(const char *, ...) ; car si ce n'est pas le cas, l'appel à cette fonction est erroné (trop d'arguments). Elle devrait prendre un pointeur constant car il n'y a aucune raison pour que log_out() modifie la chaîne de caractères. Bien sûr, vous pourriez dire que vous voulez passer une seule chaîne de caractères à la fonction - mais vous ne pouvez pas. Une option consiste alors à écrire une version varargs de la fonction log_out().

130voto

akappa Points 5452

Utilice sprintf . (Ceci n'est PAS sûr, mais l'OP a demandé une réponse en ANSI C.) Voir les commentaires pour une version sûre).

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

Écrire des données formatées dans une chaîne de caractères Composer une chaîne de caractères avec le même texte qui serait imprimé si le format était utilisé sur printf, mais au lieu d'être d'être imprimé, le contenu est stocké sous forme de chaîne C dans le tampon pointé par str.

La taille de la mémoire tampon doit être suffisante pour contenir l'intégralité de l'ensemble de l'opération. chaîne résultante (voir snprintf pour une version plus sûre).

Un caractère nul de fin est automatiquement ajouté après le contenu.

Après le paramètre de format, la fonction attend au moins autant de d'arguments supplémentaires nécessaires au formatage.

Paramètres :

str

Pointeur vers un tampon où la chaîne C résultante est stockée. Le tampon doit être suffisamment grand pour contenir la chaîne de caractères résultante.

format

C qui contient une chaîne de format qui suit la même spécifications que le format de printf (voir printf pour plus de détails).

... (additional arguments)

Selon la chaîne de format, la fonction peut s'attendre à une séquence de arguments supplémentaires, chacun contenant une valeur à utiliser pour remplacer un spécificateur de format dans la chaîne de format (ou un pointeur vers un fichier de stockage). spécificateur de format dans la chaîne de format (ou un pointeur vers un emplacement de de stockage, pour n). Il doit y avoir au moins autant de ces arguments que le nombre de valeurs spécifiées dans la chaîne de format. que le nombre de valeurs spécifiées dans les spécificateurs de format. Les arguments supplémentaires de supplémentaires sont ignorés par la fonction.

Exemple :

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

46 votes

Oups ! Dans la mesure du possible, utilisez les fonctions de variation 'n'. Par exemple, snprintf. Elles vous feront compter la taille de vos tampons et vous assureront ainsi contre les dépassements.

7 votes

Oui, mais il a demandé une fonction C ANSI et je ne suis pas sûr que snprintf soit ansi ou même posix.

8 votes

Ah. J'ai raté ça. Mais je suis sauvé par la nouvelle norme : les variantes 'n' sont officielles dans C99. FWIW, YMMV, etc.

28voto

cmaster Points 7460

Si vous disposez d'un système compatible POSIX-2008 (tout Linux moderne), vous pouvez utiliser la méthode sûre et pratique suivante asprintf() fonction : Il va malloc() suffisamment de mémoire pour vous, vous n'avez pas à vous soucier de la taille maximale des chaînes. Utilisez-le comme ceci :

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

C'est l'effort minimal que vous pouvez fournir pour construire la chaîne de manière sûre. Le site sprintf() Le code que vous avez donné dans la question est profondément défectueux :

  • Il n'y a pas de mémoire allouée derrière le pointeur. Vous écrivez la chaîne de caractères à un emplacement aléatoire de la mémoire !

  • Même si vous aviez écrit

    char s[42];

    vous auriez de gros problèmes, car vous ne sauriez pas quel chiffre mettre entre parenthèses.

  • Même si vous aviez utilisé la variante "sûre" snprintf() vous courrez toujours le risque que vos chaînes de caractères soient tronquées. Lors de l'écriture dans un fichier journal, il s'agit d'un problème relativement mineur, mais il est possible de couper précisément l'information qui aurait été utile. De plus, cela coupera le caractère de fin de ligne, collant la ligne de journal suivante à la fin de votre ligne écrite sans succès.

  • Si vous essayez d'utiliser une combinaison de malloc() y snprintf() pour produire un comportement correct dans tous les cas, on se retrouve avec environ deux fois plus de code que celui que j'ai donné pour asprintf() et reprogrammer la fonctionnalité de asprintf() .


Si vous cherchez à fournir une enveloppe de log_out() qui peut prendre un printf() la liste des paramètres de style elle-même, vous pouvez utiliser la variante vasprintf() qui prend un va_list comme argument. Voici une implémentation parfaitement sûre d'un tel wrapper :

//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");
    }
}

2 votes

Notez que asprintf() ne fait pas partie du standard C 2011 ni de POSIX, ni même de POSIX 2008 ou 2013. Elle fait partie de la TR 27431-2 : voir Utilisez-vous les fonctions "sûres" de la norme TR 24731 ?

0 votes

Fonctionne comme un charme ! J'étais confronté à un problème lorsque la valeur "char*" n'était pas correctement imprimée dans les journaux, c'est-à-dire que la chaîne de caractères formatée n'était pas appropriée.

14voto

Michael Burr Points 181287

Il me semble que vous voulez être en mesure de passer facilement une chaîne créée à l'aide d'un formatage de type printf à la fonction dont vous disposez déjà et qui prend une simple chaîne. Vous pouvez créer une fonction enveloppante en utilisant stdarg.h installations et vsnprintf() (qui peut ne pas être facilement disponible, en fonction de votre compilateur/plateforme) :

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

Pour les plateformes qui ne fournissent pas une bonne implémentation (ou n'importe quelle implémentation) de la fonction snprintf() famille de routines, j'ai utilisé avec succès un domaine quasi public snprintf() de Holger Weiss .

0 votes

Actuellement, on peut envisager d'utiliser vsnprintf_s.

3voto

Jonathan Leffler Points 299946

Si vous avez le code pour log_out() réécrivez-le. Très probablement, vous pouvez le faire :

static FILE *logfp = ...;

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

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

Si des informations de journalisation supplémentaires sont nécessaires, elles peuvent être imprimées avant ou après le message affiché. Cela permet d'économiser l'allocation de mémoire et les tailles de tampon douteuses et ainsi de suite. Vous aurez probablement besoin d'initialiser logfp à zéro (pointeur nul) et de vérifier s'il est nul et d'ouvrir le fichier journal comme il se doit - mais le code dans la version existante de l log_out() devrait s'occuper de ça de toute façon.

L'avantage de cette solution est que vous pouvez simplement l'appeler comme s'il s'agissait d'une variante de printf() ; en effet, il s'agit d'une variante mineure de printf() .

Si vous n'avez pas le code pour log_out() Dans ce cas, il est possible de le remplacer par une variante telle que celle décrite ci-dessus. La possibilité d'utiliser le même nom dépendra de votre cadre d'application et de la source ultime de l'objet actuel. log_out() fonction. Si elle se trouve dans le même fichier objet qu'une autre fonction indispensable, vous devrez utiliser un nouveau nom. Si vous ne parvenez pas à la reproduire exactement, vous devrez utiliser une variante comme celles données dans d'autres réponses qui alloue une quantité appropriée de mémoire.

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

Évidemment, vous appelez maintenant le log_out_wrapper() au lieu de log_out() - mais l'allocation de mémoire et tout le reste est fait une fois. Je me réserve le droit de surallouer l'espace d'un octet inutile - je n'ai pas vérifié deux fois si la longueur retournée par vsnprintf() inclut ou non la terminaison null.

0voto

David Thornley Points 39051

Je ne l'ai pas fait, donc je vais juste indiquer la bonne réponse.

Le C prévoit des fonctions qui prennent un nombre indéterminé d'opérandes, en utilisant la fonction <stdarg.h> en-tête. Vous pouvez définir votre fonction comme void log_out(const char *fmt, ...); et obtenir le va_list à l'intérieur de la fonction. Vous pouvez alors allouer de la mémoire et appeler vsprintf() avec la mémoire allouée, le format, et va_list .

Alternativement, vous pourriez utiliser ceci pour écrire une fonction analogue à sprintf() qui allouerait de la mémoire et renverrait la chaîne formatée, en la générant plus ou moins comme ci-dessus. Il s'agirait d'une fuite de mémoire, mais si vous ne faites que vous déconnecter, cela n'a pas d'importance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X