Home > Back-end >  custom sprintf in C
custom sprintf in C

Time:07-08

I'm trying to write a custom sprintf to format strings, with no need to pass a variable to write output to.

What I'm doing is traversing the given string with a for loop, finding % char, moving a char forward, switch-case that next char

Here

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

But I can't realloc p_concat_str length and can't append the argument returned by va_arg to p_concat_str with strcat

This is what I got

-- 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

CodePudding user response:

you can use vsnprintf() inside your concat() function to detect length of output string. If you don't pass the destination buffer and its size, vsnprintf() will only count the length of the output string and return it.

I think this way is relatively optimal, less malloc/realloc will be required for the program and the code will be slightly better readable.

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. The GNU extension of the C library contains the function vasprintf which can be used to achieve the same result:

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

CodePudding user response:

This seems to work.
Use another pointer to append to the string.

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

char *concat(char *p_format, ...) {
    size_t len = strlen ( p_format);
    size_t length = len;
    char *p_concat_str = malloc ( length   1); // allocate for characters in format
    char *cur = p_concat_str; // pointer for appending
    char *p_arg_str = NULL;

    va_list args;
    va_start ( args, p_format);

    for (size_t i = 0; i < len;   i) {
        *cur = p_format[i]; // assign character
        *(cur   1) = 0; // zero terminate
        if (p_format[i] == '%') {
            *cur = 0; // zero terminate
            i  ; // advance past %

            switch (p_format[i]) {
                case 's':
                    p_arg_str = va_arg(args, char *); // get next arg
                    size_t format_len = strlen ( p_arg_str);
                    length  = format_len; // accumulate length
                    p_concat_str = realloc ( p_concat_str, length   1);
                    strcpy ( cur, p_arg_str);
                    cur  = format_len; // advance append pointer
                    break;
            }
        }
        cur  = *cur != 0; //advance if not terminating zero
    }

    va_end ( args);

    return p_concat_str;
}

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