J'ai une fonction qui accepte une chaîne, c'est-à-dire:
void log_out(char *);
En l'appelant, je dois créer une chaîne formatée à la volée comme:
int i = 1;
log_out("some text %d", i);
Comment est-ce que je fais ceci dans 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);
Un moyen de raccourcir ça?
Utilisez sprintf .
int sprintf ( char * str, const char * format, ... );
Écrire des données formatées dans une chaîne Compose une chaîne avec le même texte que celui qui serait imprimé si le format était utilisé sur printf, mais au lieu d'être imprimé, le contenu est stocké sous la forme d'une chaîne C dans la mémoire tampon indiquée par str.
La taille de la mémoire tampon doit être suffisamment grande pour contenir la chaîne résultante complète (voir snprintf pour une version plus sûre).
Un caractère nul final est automatiquement ajouté après le contenu.
Après le paramètre format, la fonction attend au moins autant d'arguments supplémentaires que nécessaire pour le format.
str
Pointeur sur une mémoire tampon dans laquelle la chaîne C résultante est stockée. La mémoire tampon doit être suffisamment grande pour contenir la chaîne résultante.
format
Chaîne C qui contient une chaîne de format qui suit les mêmes spécifications que le format dans printf (voir printf pour plus de détails).
... (additional arguments)
En fonction de la chaîne de format, la fonction peut attendre une séquence d'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 emplacement de stockage, pour n). Il devrait y avoir au moins autant d'arguments que le nombre de valeurs spécifiées dans les spécificateurs de format. Les arguments supplémentaires sont ignorés par la fonction.
// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello" "world");
Si vous avez un système compatible POSIX-2008 (tout Linux moderne), vous pouvez utiliser la fonction sûre et pratique asprintf()
: elle _ malloc()
assez de mémoire pour vous, vous n'avez pas besoin se soucier de la taille maximale de la chaîne. 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 minimum que vous pouvez obtenir pour construire la chaîne de manière sécurisée. Le code sprintf()
que vous avez donné dans la question est profondément imparfait:
Il n'y a pas de mémoire allouée derrière le pointeur. Vous écrivez la chaîne à un emplacement aléatoire dans la mémoire!
Même si tu avais écrit
char s[42];
vous auriez de gros problèmes, car vous ne pouvez pas savoir quel numéro mettre entre parenthèses.
Même si vous aviez utilisé la variante "sûre" snprintf()
, vous courriez toujours le risque que vos chaînes soient tronquées. Lors de l'écriture dans un fichier journal, c'est une préoccupation relativement mineure, mais cela risque de couper précisément l'information qui aurait été utile. En outre, cela supprimera le dernier caractère final, en collant la ligne suivante du journal à la fin de votre ligne écrite sans succès.
Si vous essayez d'utiliser une combinaison de malloc()
et snprintf()
pour produire un comportement correct dans tous les cas, vous obtenez environ deux fois plus de code que ce que j'ai donné pour asprintf()
, et reprogrammer fondamentalement la fonctionnalité de asprintf()
.
Si vous envisagez de fournir un wrapper de log_out()
pouvant prendre lui-même une liste de paramètres de style printf()
, 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");
}
}
Il me semble que vous souhaitez pouvoir passer facilement une chaîne créée avec un formatage de style printf à la fonction que vous avez déjà qui prend une chaîne simple. Vous pouvez créer une fonction wrapper en utilisant les fonctions stdarg.h
Et vsnprintf()
(qui peuvent ne pas être facilement disponibles, selon votre compilateur/plate-forme):
#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 plates-formes qui n'offrent pas une bonne implémentation (ou toute implémentation) de la famille de routines snprintf()
, j'ai utilisé avec succès n domaine presque public snprintf()
de Holger Weiss .
Si vous avez le code pour log_out()
, réécrivez-le. Très probablement, vous pouvez 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 mémoire tampon douteuses, etc. Vous devez probablement initialiser logfp
à zéro (pointeur null) et vérifier si elle est nulle et ouvrir le fichier journal comme il convient - mais le code dans la log_out()
existante devrait en tenir compte 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()
, demandez-vous si vous pouvez le remplacer par une variante telle que celle décrite ci-dessus. Que vous utilisiez le même nom ou non dépendra de votre infrastructure d'application et de la source ultime de la fonction log_out()
actuelle. S'il se trouve dans le même fichier objet qu'une autre fonction indispensable, vous devrez utiliser un nouveau nom. Si vous ne pouvez pas savoir comment le répliquer exactement, vous devrez utiliser une variante semblable à celle donnée dans d'autres réponses qui alloue une quantité de mémoire appropriée.
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 la log_out_wrapper()
au lieu de log_out()
- mais l'allocation de mémoire, etc., est effectuée une fois. Je me réserve le droit de sur-allouer de l'espace d'un octet inutile - je n'ai pas vérifié si la longueur renvoyée par vsnprintf()
inclut ou non le caractère final.
Ne pas utiliser sprintf.
Il débordera de votre mémoire tampon de chaîne et plantera votre programme.
Toujours utiliser snprintf
Je n'ai pas fait cela, alors je vais simplement indiquer la bonne réponse.
C contient des dispositions pour les fonctions qui prennent des nombres d'opérandes non spécifiés, en utilisant l'en-tête <stdarg.h>
. Vous pouvez définir votre fonction comme void log_out(const char *fmt, ...);
et obtenir le va_list
Dans la fonction. Ensuite, vous pouvez 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. Ce serait une fuite de mémoire, mais si vous vous déconnectez, ce n'est peut-être pas grave.