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
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
, andva_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 typeva_list
. The object ap may be passed as an argument to another function; if that function invokes theva_arg
macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to theva_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 theva_start
macro, or the function containing the expansion of theva_copy macro
, that initialized theva_list
ap. Theva_end
macro may modify ap so that it is no longer usable (without being reinitialized by theva_start
orva_copy
macro). If there is no corresponding invocation of theva_start
orva_copy
macro, or if theva_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)));