Format une string sans l'afficher

a marqué ce sujet comme résolu.

Bonjour,

je me suis demandé s’il était possible en C de formater une string et la renvoyer, sans l’afficher ?

Je sais que je pourrais l’implémenter avec :

1
2
3
4
5
6
7
8
void foofmt( char* fmt, ...){
    char buf[100];     // this should really be sized appropriately
    va_list vl;
    va_start(vl, fmt);
    vsnprintf( buf, sizeof( buf), fmt, vl);
    va_end( vl);
    return buf;
}

(Source : https://stackoverflow.com/questions/804288…)

Mais je voulais savoir s’il n’y a pas une fonction "de base" qui fait cela ?

Merci d’avance.

+0 -0

Coucou,

sprintf ne fait pas le taf ?

Je rajoute un exemple quand même :

1
2
3
int a = 11;
char str[100] = "";
sprintf(str, "%d", a);
+4 -0

Salut,

oui, mais il faut passer par une définition de str[100] avant. Je chercherai une fonction qui retourne elle même un char* …

J’utilise cela en attendant, mais j’aimerai faire quelque chose comme :

1
printf("%s\n", strFormat("%i %i %i", 1, 2, 3));

Bon, ici ce n’est pas très utile quoi ;)

Ça n’existe pas pour plusieurs raisons.

La première, c’est que ce n’est pas simple. Il faut déterminer la taille de la chaine à l’avance, donc il faut faire au minimum 2 fois l’opération et c’est un processus long et inutile.
La deuxième raison c’est que pour cela, tu dois utiliser l’allocation dynamique. Or quand on utilise l’allocation dynamique, on doit s’assurer de toujours libérer l’espace que l’on a aloué.

Dans ton code :

1
printf("%s\n", strFormat("%i %i %i", 1, 2, 3));

Si strFormat retourne une adresse alouée dynamiquement alors tu as perdu cette adresse et tu ne peux donc pas libérer la mémoire aloué dynamiquement. Ça s’appele une fuite de mémoire.

En bref, utilise sprintf. Il existe des méthodes pour créer la fonction strFormat en évitant une fuite de mémoire mais elles sont toutes plus ou moins salles. Il existe également une méthode pour écrire sans afficher, c’est d’écrire dans un fichier temporaire (stocker en RAM) puis de relire ensuite le fichier temporaire, ça peu être une solution dans certains cas (la fonction est tmpfile), mais souvent, c’est overkill.

PS: Je viens de regarder ton code premier code, il est faux pour 2 raisons. La première c’est que la fonction est de type void mais retourne quelquechose. La seconde raison, c’est qu’elle retourne un tableau aloué statiquement sur la pile, à la fin de la fonction. Le tableau n’existe plus et son adresse pointeura vers une zone désormais invalide.

+2 -0

Comme alternative à sprintf, et si tu es prêt à utiliser une extension GNU, il y a asprintf qui alloue dynamiquement la mémoire requise pour la nouvelle chaîne de caractères. Par contre le commentaire d’ache s’applique toujours: il faut libérer la mémoire retournée par la fonction.

+1 -0

Note qu’on pourrait « corriger » ta fonction en mettant un static devant char buf[100], mais ce ne serait franchement pas une bonne pratique (rien que le 100 en dur ferait frémir n’importe qui).

Une alternative (C11-) standard pour recoder asprintf consisterait à récupérer la taille idéale que devrait avoir buf avec vsnprintf(NULL, 0, fmt, vl), faire l’allocation dynamique et enfin appeler vnsprintf « pour de vrai » (on pourra avoir besoin de va_copy).

@Lucas-84: /o\ Non pas le static :lol:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

char* strFormat( char fmt[], ...) {
   static char* tab = NULL;
   va_list vl, vl2;

   va_start(vl, fmt);
   va_copy(vl2, vl);

   int size = vsnprintf( NULL, 0, fmt, vl);
   char* tmpTa = NULL;

   if( size > 0 ) 
       realloc(tab, size+1);

   if( tmpTab ) {
       tab = tmpTab;
       vsprintf(tab, fmt, vl2);
   } else {
       free(tab);
       tab = NULL;
   }
   va_end(vl);
   va_end(vl2);

   return tab;
}

void freeFormat(void) {
   free(strFormat(""));
}

int main(int argc, char* argv[]) {
   atexit(freeFormat);
   puts( strFormat("%d %d %d", 5, 4, 3) );
   puts( strFormat("%d %d %d", 1, 2, 3) );

   return EXIT_SUCCESS;
}

Horrible :lol: C99 valide.

Edit: Vérification du code de sortie de vsprintf. Merci Taurre ^^

+0 -0

(rien que le 100 en dur ferait frémir n’importe qui).

Lucas-84

Pourquoi ? Est-ce qu’il est question d’utiliser une constante via define ou le fait qu’un buffer à taille statique est potentiellement sujet à des débordements ? M’est avis qu’on est large pour convertir un entier en ASCII, même si on n’est jamais trop prudent.

(rien que le 100 en dur ferait frémir n’importe qui).

Lucas-84

Pourquoi ? Est-ce qu’il est question d’utiliser une constante via define ou le fait qu’un buffer à taille statique est potentiellement sujet à des débordements ? M’est avis qu’on est large pour convertir un entier en ASCII, même si on n’est jamais trop prudent.

Ge0

Je pensais au problème du débordement (qui n’en est pas un, tant qu’on utilise vsnprintf… si on contrôle plus la taille, là ça devient carrément dangereux). A priori on parlait pas d’une fonction juste pour convertir un entier en ASCII, mais d’un truc plutôt générique (par exemple @ez613 voulait l’utiliser pour afficher 3 entiers, ce qui est déjà différent). De manière générale, je trouve ça pas vraiment naturel d’imposer une limite en dur pour ce genre de fonctions utilitaires, qui sont censées toujours faire leur boulot correctement. On a vite fait d’oublier la constante de l’appelé quand on est l’appelant… Bon après, si c’est pour bidouiller, ça pose pas de problème.

+0 -0

Salut,

1
size_t size = vsnprintf( NULL, 0, fmt, vl);

J’en vois un qui ne vérifie pas les cas d’erreur ! :pirate:

The snprintf function returns the number of characters that would have been written had n been sufficiently large, not counting the terminating null character, or a negative value if an encoding error occurred. Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less than n.

ISO/IEC 9899:TC3, N1256, 7.19.6.5, The snprintf function, al. 3, p. 291
+0 -0

Comme c’est du C, il faut faire attention à beaucoup de choses en même temps. En particulier si tu utilises un buffer static, ta fonction risquera fortement de ne pas être thread safe et tu ne gagneras rien (un buffer local, ça ne pose pas plus de problème de performance).

En plus de ça, si tu prends des entrées utilisateurs et que tu ne précises pas la taille, tu peux potentiellement être sensible à des attaques par dépassement.

Ok, merci pour les réponses. J’avoue que je n’ai pas compris toutes les subtilités; je vais donc uiliser snprintf ou asprintf.

Vu que le principal problème est de libérer la mémoire, j’aurai une question subsidiaire: est-ce que j’ai besoin de free() la mémoire d’un buffer utilisé dans une fonction, ou bien dès que le processus sort de la fonction il libère sa mémoire ?

Merci d’avance !

+0 -0

Quand tu utilises un buffer "static", tu n’alloues pas de mémoire donc c’est bon. Quand utilises une fonction, regarde sa documentation. Typiquement asprintf te dit :

Les fonctions asprintf() et vasprintf() sont similaires à sprintf(3) et vsprintf(3), si ce n’est qu’elles allouent une chaîne de caractères de taille suffisante pour contenir la sortie, y compris l’octet NULL terminal, et qu’elles renvoient un pointeur vers cette chaîne via le premier paramètre. Ce pointeur devra être passé en paramètre à free(3) afin de libérer la zone allouée lorsqu’elle ne sera plus nécessaire.

Il existe 2 types d’allocation. L’allocation statique, et dynamique.

Statique, c’est lorsque tu utilises une variables, un tableau, …

1
2
int a = 0;
char tab[256] = "";

Ces variables sont stockées dans ce qu’on appele la pile. Lorsque le programme est chargé par le Système d’Exploitation, celui-ci aloue un espace de taille fixe pour le programme. Cet espace s’appel la pile. Ça peut paraitre évident mais si ça s’appele la pile, c’est que ça fonctione comme la structure de donnée du même nom vie-à-vie des appeles aux fonctions. On ne s’occupe pas de cet espace, c’est le SE qui s’en occupe.

Étant donné que la pile à une taille limité, on peut demander au SE d’alouer de l’espace mémoire aileurs que dans la pile. C’est l’allocation dynamique. On utilise pour cela les fonctions de la fammile de malloc. On appele cet espace le tas. L’allocation n’y est pas géné par le SE (pas uniquement). On doit absolument signaler aux SE que l’on utilise plus un espace mémoire. On le fait en utilisant la fonction free (ou realloc avec 0 en taille).

Tant qu’on utilise de l’allocation statique, on ne s’occupe presque pas de la mémoire. Quand on fait de l’allocation dynamique, on est plus libre mais on doit absolument faire attention de bien tout gérer.

Bref, si ton buffer est :

1
char tab[256] = "";

Alors tu n’as pas à le libérer puisque tu n’as pas à utiliser malloc. Par-contre, tu dois savoir qu’il a une “porté” locale1. C’est-à-dire que la durée de vie de cette variable est cantonné aux crochets qui l’entoure :

1
2
3
4
5
6
7
8
char* t = NULL;
{
   char tab[256] = "";
   t = tab;
   // Ici tab existe
   // t à du sens
}
// Ici, tab n'existe pas et le pointeur t n'a plus de sens

De même :

1
2
3
4
char* foo(void) {
  int tab[11] = {0};
  return tab;
}

Ici, retourné tab est une grave erreur. Ça pourrait fonctionner comme ne pas marcher. tab a une durée de ie limitée et à la fin de l’execution de la fonction, tab n’existe plus et serra rapidement écrasé.

Les variables static :

1
2
3
void food(void) {
  static int tab[11] = {0};
}

Ici, le tableau (ça pourrait être n’importe qu’elle variable hors VLAs) à une portée limitée à la fonction mais une durée de vie globale (comme une variable globale), c’est-à-dire que retournée une référence vers la variable à du sens.

Bref, j’espère qu’avec tout ça tu peux saisir toutes les subtilités ^^

Attention avec asvprintf. Ce n’est pas une fonction standard. Ce qui implique que l’existance de cette fonction n’est pas assurée.

Voila, j’ai fini mon monologue >_<"


  1. Pour les variables non statiques, la porté = la durée de vie. On a tendance à mélanger les deux. 

+0 -0

Il existe 2 types d’allocation. L’allocation statique, et dynamique.

ache

Mmm… Il y a plutôt trois type d’allocation :

  1. L’allocation automatique réalisée, comme tu l’as dis, sur la pile ;
  2. L’allocation statique, réalisée lors du chargement du programme (d’où le fait que les variables de classe de stockage statique ne peuvent se voir initialisées qu’avec des contantes et doivent avoir une taille déterminée à l’inverse des deux autres) ; et
  3. l’allocation dynamique effectuée, comme tu l’as précisé, sur le tas.
+0 -0

J’ai toujours vu l’allocation statique comme étant simplement les premiers éléments à être chargés sur la pile. Ce qui fait que je ne fais pas la distinction entre les deux alors que l’on devrait effectivement.

Question du coup, la pile comprend-t-elle les segments de code ? Quid des constantes ?

+0 -0

J’ai toujours vu l’allocation statique comme étant simplement les premiers éléments à être chargés sur la pile. Ce qui fait que je ne fais pas la distinction entre les deux alors que l’on devrait effectivement.

Question du coup, la pile comprend-t-elle les segments de code ? Quid des constantes ?

ache

De manière générale, je pense qu’il faut bien voir le C comme un langage de haut niveau, qui définit une façon abstraite de fonctionner (qui induit d’ailleurs de vrais casse-tête quant aux optimisations, notamment au niveau de la programmation concurrente) sans lien réel avec ce qu’il y a en-dessous. De ce point de vue abstrait, il y a trois types d’allocations (ou plutôt de durées de vie) : automatique, statique et dynamique (bon, dans les faits, depuis la norme C11 y en a 4 avec en plus les objets thread local, qui ont une durée de vie liée à leur fil d’exécution, mais bref).

Maintenant, si l’on sort du cadre du C et que l’on se centre sur GNU/Linux et les *BSD :

  • Les objets alloués automatiquement sont stockés sur la pile dans la partie « haute » de la mémoire (comprendre : les adresses les plus élevées) et chaque allocation fait « descendre » la pile.
  • Les objets alloués dynamiquement se situent sur le tas qui est avant la pile (donc plus bas dans les adresses) et, lui, « monte » à chaque allocation (ce qui présente d’ailleurs un risque car il est possible que le tas et la pile se rejoignent, mais les OSs sont censés gérer cela).
  • Les objets alloués statiquement se situent dans les adresses du bas, soit dans la partie comprenant le code lui-même (cela peut être le cas si les objets sont constants comme les chaînes de caractères, la partie comprenant le code ne pouvant pas être modifiée), soit après dans une partie dédiée.
+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte