Search code examples
cprintfvariadic-functions

Problem with the implementation of va_arg in my function


I am currently debugging my printf project for school, basically i just have to redo the function but i am encountering a problem that i can't understand.

Here is my code :

#include "ft_printf.h"

int ft_putchar(int c)
{
    write(1, &c, 1);
    return (1);
}

int ft_format(va_list args, const char format)
{
    if (format == 'c')
        return (ft_putchar(va_arg(args, int)));
    return (-1);
}

int ft_printf(const char *format, ...)
{
    size_t  i;
    int     print_length;
    va_list args;

    i = 0;
    print_length = 0;
    va_start(args, format);
    while (format[i])
    {
        if (format[i] == '%')
        {
            i++;
            if (format[i] != '\0')
            {
                print_length += ft_format(args, format[i]);
            }
        }
        else
            print_length += ft_putchar(format[i]);
        i++;
    }
    va_end(args);
    return (print_length);
}

int main(void)
{
    ft_printf("l%cl%cl%cl%c\n", 'a', 'b', 'c', 'd');
    printf("l%cl%cl%cl%c\n", 'a', 'b', 'c', 'd');
}

The result :

ft_printf : lalalala | printf : lalblcld

I cant understand why it doesn't work properly when i try to print many arguments, so if someone can help me figure out where the problem is..

What i tried :

I tried to add va_arg(args, int); after calling ft_format and it worked properly ! but i still don't understand why this case isn't treated directly in my ft_format function ?

int ft_printf(const char *format, ...)
{
    size_t  i;
    int     print_length;
    va_list args;

    i = 0;
    print_length = 0;
    va_start(args, format);
    while (format[i])
    {
        if (format[i] == '%')
        {
            i++;
            if (format[i] != '\0')
            {
                print_length += ft_format(args, format[i]);
                                a_arg(args, int);
            }
        }
        else
            print_length += ft_putchar(format[i]);
        i++;
    }
    va_end(args);
    return (print_length);
}

Output :

ft_printf : lalblcld | printf : lalblcld


Solution

  • From C11 7.16 Variable arguments <stdarg.h>: (Emphasis and formatting mine)

    The type declared is

        va_list
    

    which is a complete object type suitable for holding information needed by the macros va_start, va_arg, va_end, and va_copy. If access to the varying arguments is desired, the called function shall declare an object (generally referred to as ap in this subclause) having type va_list. The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

    and

    It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.

    So after ft_format returns, va_end must be called on args to restore it from an indeterminate state.

    But, C11 7.16.1.3 The va_end macro also has this to say: (Emphasis and formatting mine)

    The va_end macro facilitates a normal return from the function whose variable argument list was referred to by the expansion of the va_start macro, or the function containing the expansion of the va_copy macro, that initialized the va_list ap. The va_end macro may modify ap so that it is no longer usable (without being reinitialized by the va_start or va_copy macro). If there is no corresponding invocation of the va_start or va_copy macro, or if the va_end macro is not invoked before the return, the behavior is undefined.

    As far as I can tell, you have two options:

    Use va_copy to create a copy of your va_list object in its current state

    if (format[i] != '\0')
    {
        va_list tmp;
        va_copy(tmp, args);
        print_length += ft_format(tmp, format[i]);
        va_end(tmp);
    }
    

    and pass that object to ft_format.

    Or, define ft_format as

    int ft_format(va_list *args, const char format)
    

    and pass it a pointer to your va_list object

    print_length += ft_format(&args, format[i]);
    

    and

    return (ft_putchar(va_arg(*args, int)));