Search code examples
cprintfc-strings

Implementing your own printf


I am trying to implement the C printf but with instead of %s, I use {s}. Instead of %d ... {d}. Instead of %c ... {c}(Somewhat like Python/C#/Java's positional string format args but instead of numeric positions there are specifications of the data type). It should also escape additional curly brackets such that {{} becomes {. It all works except when I pass in {{ as part of a test. Valgrind informs me that This is probably caused by your program erroneously writing past the end of a heap block and corrupting heap metadata. This is the code:

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

char *mr_asprintf(const char *format, ...) {
    if (strlen(format) == 0) { return calloc(1, sizeof(char)); }
    char buf[strlen(format) * 2];
    memset(buf, 0, sizeof(buf));
    va_list args;
    va_start(args, format);
    while (*format != '\0') {
        if (*format == '{') {
            format++;
            if (*format == '{') {
                sprintf(buf + strlen(buf), "%c", '{');
            } else if (*format == 's') {
                sprintf(buf + strlen(buf), "%s", va_arg(args, const char*));
            } else if (*format == 'i') {
                sprintf(buf + strlen(buf), "%d", va_arg(args, int));
            }
            if (*format++ == '}') {}
        } else if (*format == '}') {
            sprintf(buf + strlen(buf), "%c", '}');
        } else {
            sprintf(buf + strlen(buf), "%c", *format);
        }
        format++;
    }
    va_end(args);
    return strdup(buf);
}

These are the tests:

printf("'%s'\n", mr_asprintf("Gaius Julius Caesar Augustus Germanicus"));
printf("'%s'\n", mr_asprintf("Nickname: {s}", "Caligula")); 
printf("'%s'\n", mr_asprintf("Reign: {i} AD - {i} AD", 37, 41)); 
printf("'%s'\n",
           mr_asprintf("born: {s} {i}, {i} in {s}", "August", 31, 12, "Antium")); 
printf("'%s'\n", mr_asprintf("Roman emperor #{i}", 3)); 
printf("'%s'\n", mr_asprintf("Roman emperor #{i}}", 3)); 
printf("'%s'\n", mr_asprintf(""));
printf("'%s'\n", mr_asprintf("}bae}ccac {i}bdbb{i}bb{i}}dd b", 394603702, 511917921,
                                 721200806)); 
printf("'%s'\n", mr_asprintf("{{"));

Expected Output:

'Gaius Julius Caesar Augustus Germanicus'
'Nickname: Caligula'
'Reign: 37 AD - 41 AD'
'born: August 31, 12 in Antium'
'Roman emperor #3'
'Roman emperor #3}'
''
'}bae}ccac 394603702bdbb511917921bb721200806}dd b'
'{'

Actual Output:

  1. gcc 10.3.0
'Gaius Julius Caesar Augustus Germanicus'
'Nickname: Caligula'
'Reign: 37 AD - 41 AD'
'born: August 31, 12 in Antium'
'Roman emperor #3'
'Roman emperor #3}'
''
'}bae}ccac 394603702bdbb511917921bb721200806}dd b'
'{Gaius Julius Caesar Augustus Germanicus'
  1. clang 8
'Gaius Julius Caesar Augustus Germanicus'
'Nickname: Caligula'
'Reign: 37 AD - 41 AD'
'born: August 31, 12 in Antium'
'Roman emperor #3'
'Roman emperor #3}'
''
'}bae}ccac 394603702bdbb511917921bb721200806}dd b'
'{}}'

As you can see, all but the last tests work properly. What could I be doing wrong? Also can you suggest ways to improve my code and cut down on memory leaks?


Solution

  • After a lot of refactoring, what ultimately worked was checking whether the next character is not NUL, then incrementing the pointer.