Forum Programmation.c tableau comme argument à vsprintf

Posté par  .
Étiquettes : aucune
0
20
juil.
2006
Je cherche à construire un buffeur pour une requete SQL et je ne connais pas le nombre d'arguments que je doit passer.
EXEMPLE, SI j'ai un nombre d'argument fixe, je fait :
char param1[12], param2[12];
char *format="UPDATE t_table set col1='%s', col2='%s'";
......
sprintf(buff,format,param1,param2);

Mais dans le cas present je ne sait pas quelles colonnes seront mise a jour,
Je construis ma chaine "UPDATE .." par concatenation avec les colonnes necessaires;
char format[100]="UPDATE t_table set "; int i=0;
char *param[8];

if( bon1 ){ strcat( fromat," col1='%s',"); param[i++]=param1;}
if( bon2 ){ strcat( fromat," col2='%s',"); param[i++]=param2;}
*param[i]=NULL; /* avec ou sans ça meme resultat */
..
/* et je voudrais faire un truc comme ça */
vsprintf( buff,format,param);
ET ça sa ne marche pas (Memory fault) vsprintf veut un va_list pour troisième argument.
Mon problème est donc de donner un tableau à "v/sprintf" plutot qu'une liste en troisieme argument.

Si je mets *param[i++]=NULL; au début , pas de Memory fault mais rien à la place des %s.

Si je mets
va_start(ap, param[0]); /*ou va_start(ap, param); */
J'ai erreur à la compilation : "symbole non defini : ... "
je crois que les "..." correspondent aux ... donnés en 2eme parametre à une fonction dont le nombre d'arguments est variable.
(ap est bien declaré en va_list)

REM sur un autre forum on m'a donner une solution pour Windows, et ils sont tres heureux de prétendent pour faire ça sous Windows et que c'est impossible sous Linux .........
@+
  • # Re: tableau comme argument à vsprintf

    Posté par  . Évalué à 3.

    La construction à la main d'un type va_list n'est pas du tout portable. Forcément sous Windows ça limite les choix, mais je parirai que le code qu'ils t'ont filé doit être hyper casse gueule. À ta place je reverrais légèrement ton "algo" :
    #define MAX 1024
    char buffer[MAX];
    int usage = 0;
    
    usage = sprintf(buffer, "%s", "UPDATE t_table set ");
    
    if (bon1)
    {
        // Hum, hum, fait gaffe à l'SQL injection hein ....
        usage += snprintf(buffer + usage, MAX - usage, " col1='%s',", param1);
    }
    
    if (bon2)
    {
        // Idem ici
        usage += snprintf(buffer + usage, MAX - usage, " col2='%s',", param2);
    }
    
    /* .... */
    C'est un poil plus complexe, parce qu'il y a vérification des débordements, mais si tu n'en a rien à foutre, tu pourras grandement simplifier le code ... Sinon, je ne sais pas quel SGBD tu utilises, main en général dans l'API d'accès à la base, il y a des fonctions toutes prêtes pour gérer beaucoup plus proprement ce genre de problèmes (PostgreSQL et SQLite au moins).
  • # Veni, Vidi, et pas compris ;)

    Posté par  (site web personnel) . Évalué à 2.

    Bon j'ai pas bien tout compris ce que tu voulait faire. Si tu veut une fonction capable de te generer une chaine SQL d'update pour une liste d'arguments fournis alors tu peut utiliser les va_list. Sache toutefois que tu doit fournir lors de l'appel de la fonction une information permettant de determiner la fin des arguments. Cela peut etre un nombre, un argument avec une signification spéciale ...
    #include <stdio.h>
    #include <stdarg.h>
    #include <stdlib.h>
    
    void create_update(char *table, ...) {
      char *format;
      va_list   ap;
      char *nom_col, *val_col;
      int   buf_s = 1000, i = 0;
      format = (char*) malloc (buf_s * sizeof(char));
    
      i += snprintf(format, buf_s, "UPDATE %s SET ", table);
      buf_s -= i;
      /* Analyse des arguments */
      va_start (ap, table);
    
      do {
        
        if (nom_col = va_arg(ap, char *)) {
        val_col = va_arg(ap, char *);
    
        i += snprintf(format + i, buf_s, "%s='%s', ",
                      nom_col, val_col);
        buf_s -= i;
        } while (nom_col);
      }
      va_end (ap);
    
      format[i-2] = 0;
      printf(format);
    }
    
    
    int main() {
      create_update ("toto",
                     "nom", "Steff",
                     "prenom", "aneL",
                     "niveau", "debutant", NULL);
    
      return 1;
    }
    
    Sinon si tu ne connait pas le nombre d'arguments lors de la compilation alors tu va devoir enchainer des appels a *printf comme j'ai fait dans la fopnction ci dessus. voili voila, Ce genre de connerie est quand meme plus simple a faire en utilisant un langage adapté (perl/python/pike/....) voire meme en C++ Pour ton exemple sous windows paste le lien que l'on rigole nous aussi.
  • # Petite précision pour Benoit

    Posté par  . Évalué à 1.

    Le programme doit tourner en boucle donc les colonnes à mettre à jour seront différentes entre 2 tours, par exemple une fois se sera "nom" uniquement, le coup d'après "niveau" uniquement et après tous et après ... dieu seul le sait. Donc je ne peux pas les ecrire en dur ou sinon il faut toutes les possibilitées et prendre la bonne pour chaque tour.
    (Pour ta fonction il faudrait revoir les accolades ouvrantes et fermantes car les if{..}while(..); et les do{..}va_end(ap); meme avec un niveau débutant ça fait bizarre.)
    Pour "Ce genre de connerie est quand meme plus simple a faire en utilisant un langage adapté : Perl " d'accord à 200% mais j'ai ordre de le faire en C, malheureusement pour moi.
    Pour le forum pro windows : http://www.developpez.net/forums/showthread.php?t=184360
    @+
    • [^] # Re: Petite précision pour Benoit

      Posté par  (site web personnel) . Évalué à 2.

      Effectivement, j'ai modifié la fonction dans le commentaire pour quelle gere un NULL de fin plutot qu'un arguments donnant le nombre de resultats. Enfin tout le monde aura corrigé mais bon:
          } while (nom_col);
        } while (nom_col);
        va_end (ap);
      
      La solution que ta donné Médinoc fonctionne sous linux/x86 elle aussi
      #include <stdarg.h>
      #include <stdlib.h>
      
      #ifdef __cplusplus
      #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
      #else
      #define _ADDRESSOF(v) ( &(v) )
      #endif
      
      #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
      #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
      
      #ifdef va_start
      #undef va_start
      #endif
      #define va_start _crt_va_start
      
      int main() {
       char * param[8];
       param[0] = NULL;
       param[1] = "Blabla";
       param[2] = "Test";
       param[3] = NULL;
       va_list vl;
      
       va_start(vl, param[0]);
      #if 1
       vprintf("%s => %s %d",vl);
      #else
       char const *str;
      
       while((str = va_arg(vl, char const*)) != NULL)
               puts(str);
      
      #endif
       va_end(vl);
      }
      
      Mais je te déconseille fortement cette methode parce que ce n'est absolument pas portable. Contrairement à ce que semble penser cet "Expert confirmé Senior" les valist sont dépendantes de la plateforme matérielle et non de la plateforme logicielle. Typiquement ce code fonctionne sur x86 mais à peu de chance de fonctionner sur powerpc par exemple ou plus betement sur Itanium ou en mode x86-64. Mon conseil: Utilise des appels a snprintf dans une boucle ou autre structure de controle ce sera bcp plus propre. Exemple en reprenant ton code, transforme:
      
      if( bon1 ){ strcat( fromat," col1='%s',"); param[i++]=param1;}
      
      En:
      
      
      if( bon1 ){ i += snprintf( buf + i, buf_s, "col1='%s',", param1); buf_s -=i;}
      
  • # Dernier

    Posté par  . Évalué à 1.

    Au final la façon la plus simple (et la moin sur surment) est tout simplement de recaster param et ça lui suffit. Telment simple que je n'avais meme pas osé essayer.

    if( bon1 ){ strcat( fromat," col1='%s',"); param[i++]=param1;}
    if( bon2 ){ strcat( fromat," col2='%s',"); param[i++]=param2;}
    param[i]=NULL;

    vsprintf( buff,format,(va_list) param);

    dans mon cas je n'ai que des strings c'est plus simple sinon je pense qu'il faudrait utiliser va_arg ?
    @+
    • [^] # Re: .

      Posté par  . Évalué à 2.

      P...n, tu m'étonnes après qu'on dit que le C est un langage de goret. Bon, je vois 3 fautes monumentales dans ces 5 pauvres lignes :

      - Ta conversion de param en va_list n'est pas du tout portable, j'espère que tu n'auras pas à le tester sur d'autres archie. Sympa pour ceux qui vont maintenir ça après ...

      - Utilise au moins vsnprintf, ça coute pas beaucoup plus cher, et tu éviteras un petit buffer overflow (surtout avec une taille de 100 octets). J'espère qu'il n'est pas trop critique ce code (pas d'accès public).

      - Tes paramètres passés directement à sprintf, j'espère que tu es sûr de leur contenu, parce que c'est du pain bénit pour l'injection SQL, bref une faille de sécurité monumentale. Clairement, il ne vaut mieux pas qu'il soit public ton code.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.