2 votes

sprintf personnalisé en C

J'essaie d'écrire un programme personnalisé sprintf pour formater les chaînes de caractères, sans avoir besoin de passer une variable pour écrire la sortie.

Ce que je fais est de parcourir la chaîne donnée avec un for boucle, trouver % l'omble, en faisant avancer un omble, switch - case le prochain caractère

Voici le code :

utils.c :

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>

char *concat(char *p_format, ...) {
   char *p_concat_str = calloc(1, sizeof(char));

   va_list args;
   va_start(args, p_format);

   for (unsigned int i = 0; i < strlen(p_format); ++i) {
      if (p_format[i] == '%') {
         i++;

         void *p_arg_str = va_arg(args, char *);
         /* printf("%s\n", (char *)p_arg_str); */
         p_concat_str = realloc(p_concat_str, (strlen(p_arg_str) + 1));
         switch (p_format[i]) {
            case 's':
               strcat(p_concat_str, (char *)p_arg_str);
               /* printf("%s\n", (char *)p_arg_str); */
               break;
         }
      }
      p_concat_str = realloc(p_concat_str, i + 2);
      p_concat_str[i] = p_format[i];
      p_concat_str[i + 1] = '\0';
   }

   return p_concat_str;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>

int main() {
   char *p_world = "World";
        /* Hello World */
   char *p_str = concat("Hello %s", p_world);
   printf("Formatted str: %s | its len %lu\n", p_str, strlen(p_str));
   free(p_str);
   return 0;
}

Mais je ne peux pas réallouer la longueur de p_concat_str et je ne peux pas ajouter l'argument retourné par va_arg à p_concat_str avec strcat.

Voici ce que j'ai obtenu

-- Configuring done
-- Generating done
-- Build files have been written to: /home/prxvvy/workspace/cutils/cmake-build-debug
[2/2] Linking C executable cutils
Formatted str: Hello Ws | its len 8

0voto

chqrlie Points 17105

Il y a plusieurs problèmes dans votre code :

  • la taille que vous passez à realloc est incorrect : au lieu de p_concat_str = realloc(p_concat_str, (strlen(p_arg_str) + 1)); vous devriez écrire :

      p_concat_str = realloc(p_concat_str,
                             strlen(p_concat_str) + strlen(p_arg_str) + 1);
  • vous supposez toujours qu'il y a un argument de type chaîne de caractères, avant de tester pour l'option s format de conversion.

  • vous tentez toujours de concaténer le caractère de la chaîne de format, même si vous avez manipulé un format de conversion.

  • vous réaffectez p_concat_str = realloc(p_concat_str, i + 2); et copier le caractère à p_concat_str[i] pour cela mais la chaîne de destination peut être plus grande (ou même plus petite) si des %s Les conversions ont déjà été traitées.

Voici une modification utilisant la même approche simpliste :

utils.h :

char *concat(const char *p_format, ...);

utils.c :

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"

char *concat(const char *p_format, ...) {
    char *p_concat_str = calloc(1, 1);
    if (!p_concat_str)
        return NULL;

    va_list args;
    va_start(args, p_format);
    size_t dlen = 0;
    for (size_t i = 0; p_format[i] != '\0'; ++i) {
        if (p_format[i] == '%' && p_format[i + 1] == 's') {
            const char *p_arg_str = va_arg(args, const char *);
            i++;
            if (p_arg_str == NULL)
                p_arg_str = "(null)";
            slen = strlen(p_arg_str);
            char *newp = realloc(p_concat_str, dlen + slen + 1);
            if (newp == NULL) {
                free(p_concat_str);
                p_concat_str = NULL;
                break;
            }
            p_concat_str = newp;
            memcpy(p_concat_str + dlen, p_arg_str, slen + 1);
        } else {
            char *newp = realloc(p_concat_str, dlen + 1 + 1);
            if (newp == NULL) {
                free(p_concat_str);
                p_concat_str = NULL;
                break;
            }
            p_concat_str = newp;
            p_concat_str[dlen++] = p_format[i];
            p_concat_str[dlen] = '\0';
        }
    }
    va_end(args);
    return p_concat_str;
}

main.c :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"

int main() {
   char *p_world = "World";
        /* Hello World */
   char *p_str = concat("Hello %s", p_world);
   printf("Formatted str: %s | its len %zu\n", p_str, strlen(p_str));
   free(p_str);
   return 0;
}

Vous pourriez améliorer le code en ne réallouant la chaîne de destination que lorsque vous atteignez un %s ou la fin de la chaîne, en concaténant un fragment entier de chaîne à la fois.

Si vous voulez gérer plus que juste %s Au lieu de réinventer la roue, vous pouvez utiliser un wrapper sur le module vnsprintf :

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"

char *vconcat(const char *format, va_list ap) {
    char buf[256];
    va_list args;
    int ret;
    char *str;

    va_copy(args, ap);
    ret = vsnprintf(buf, sizeof buf, format, args);
    va_end(args);

    if (ret < 0 || (str = malloc(ret + 1)) == NULL)
        return NULL;

    if (ret < (int)sizeof(buf)) {
        return memcpy(str, buf, ret + 1);
    } else {
        ret = vsnprintf(str, ret + 1, format, ap);
        if (ret < 0) {
            free(str);
            str = NULL;
        }
        return str;
    }
}

char *concat(const char *format, ...) {
    va_list args;
    char *str;

    va_start(args, format);
    str = vconcat(format, args);
    va_end(args);

    return str;
}

-1voto

Sun Dro Points 425

Vous pouvez utiliser vsnprintf() à l'intérieur de votre concat() pour détecter la longueur de la chaîne de sortie. Si vous ne passez pas le tampon de destination et sa taille, vsnprintf() ne fera que compter la longueur de la chaîne de sortie et la retournera.

Je pense que cette méthode est relativement optimale, moins malloc/realloc sera nécessaire pour le programme et le code sera légèrement plus lisible.

char *concat(char *p_format, ...)
{
    va_list args, tmp;
    va_start(args, p_format);

#ifdef va_copy
    va_copy(tmp, args);
#else
    memcpy(&tmp, &args, sizeof(va_list));
#endif

    int length = vsnprintf(0, 0, p_format, tmp);
    va_end(tmp);

    if (length <= 0)
    {
        va_end(args);
        return NULL
    }

    char *dst_buf = (char*)malloc(length + 1);
    if (dst_buf == NULL)
    {
        va_end(args);
        return NULL;
    }

    int bytes = vsnprintf(dst_buf, length + 1, p_format, args);
    va_end(args);

    if (bytes <= 0)
    {
        free(dst_buf);
        return NULL;
    }

    dst_buf[bytes] = '\0';
    return dst_buf;
}

P.S. L'extension GNU de la bibliothèque C contient la fonction vasprintf qui peuvent être utilisés pour obtenir le même résultat :

#define _GNU_SOURCE
#include <stdio.h>

char* concat(const char *p_format, ...)
{
    va_list args;
    char *dst_buf = NULL;
    int length = 0;

    va_start(args, p_format);
    length = vasprintf(&dst_buf, p_format, args);
    va_end(args);

    if (length <= 0 && dst_buf)
    {
        free(dst_buf);
        return NULL;
    }

    dst_buf[length] = '\0';
    return dst_buf;
}

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