Issue
I am having trouble getting the correct output from my custom _printf function in C. The function is supposed to print formatted output, including unsigned hexadecimal values. However, the output for unsigned hexadecimal values is incorrect when I try to use both the lowercase and uppercase format specifiers in the format string.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <stdarg.h>
#define BUF_SIZE 1024
typedef struct format_s
{
char *spec;
int (*handle)(va_list, char *, int *);
} format_t;
void convbase(unsigned long int b, unsigned int bs, char cs, char *buf, int *p)
{
int i = 0, j;
unsigned long int temp = b, div = 1;
while (temp > 0)
{
temp /= bs;
i++;
}
for (j = 0; j < (i - 1); j++)
div *= bs;
if (cs >= 'a' && cs <= 'z')
{
for (; div > 0; div /= bs, *p = *p + 1)
{
buf[*p] = ((b / div) % bs) > 9 ?
(((b / div) % bs) - 10) + 'a' : (b / div) % bs + '0';
}
}
else
{
for (; div > 0; div /= bs, *p = *p + 1)
{
buf[*p] = ((b / div) % bs) > 9 ?
(((b / div) % bs) - 10) + 'A' : ((b / div) % bs) + '0';
}
}
}
int handle_x(va_list args, char *buff, int *bufpos)
{
unsigned int x = va_arg(args, unsigned int), base = 16;
int count = 1;
char _case = 'x';
if (!va_arg(args, unsigned int *))
return (-1);
convbase(x, base, _case, buff, bufpos);
return (count);
}
int handle_X(va_list args, char *buff, int *bufpos)
{
unsigned int X = va_arg(args, unsigned int), base = 16;
int count = 1;
char _case = 'X';
if (!va_arg(args, unsigned int *))
return (-1);
convbase(X, base, _case, buff, bufpos);
return (count);
}
int (*get_fmt(char spec))(va_list, char *, int *)
{
format_t formats[] = {
{"x", handle_x}, {"X", handle_X}, {NULL, NULL}
};
int i = 0;
while (formats[i].spec != NULL)
{
if (spec == formats[i].spec[0])
return (formats[i].handle);
i++;
}
return (NULL);
}
int _printf(const char *format, ...)
{
int nc = 0, idx, bufpos = 0;
char *buff = malloc(BUF_SIZE * sizeof(char));
va_list args;
if (format == NULL || buff == NULL)
return (0);
memset(buff, 0, BUF_SIZE);
va_start(args, format);
for (idx = 0; format[idx]; idx++)
{
if (format[idx] == '%')
{
if (format[idx + 1] == '%')
{
buff[bufpos++] = '%';
}
else if (format[idx + 1] != '%')
{
if (!get_fmt(format[idx + 1])(args, buff, &bufpos))
{
buff[bufpos++] = '%';
buff[bufpos++] = format[idx + 1];
}
}
format++;
}
else
{
buff[bufpos++] = format[idx];
}
}
nc = write(1, buff, strlen(buff));
va_end(args);
return (nc);
}
int main(void)
{
unsigned int ui;
ui = (unsigned int)INT_MAX + 1024;
_printf("Lowercase hexadecimal:[%x]\n", ui);
printf("Lowercase hexadecimal:[%x]\n", ui);
_printf("Uppercase hexadecimal:[%X]\n", ui);
printf("Uppercase hexadecimal:[%X]\n", ui);
_printf("Unsigned hexadecimal:[%x, %X]\n", ui, ui);
printf("Unsigned hexadecimal:[%x, %X]\n", ui, ui);
return (0);
}
I previously wrote the convbase function to write into the buffer from a postion ahead after counting the space a value will take in the buffer:
void convbase(unsigned int b, unsigned int base, char _case, char *buf, int *bufpos)
{
int i, j, rem;
unsigned long int temp = b, init = *bufpos;
while (temp > 0)
{
temp /= base;
i++;
}
*bufpos = *bufpos + i;
if (_case >= 'a' && _case <= 'z')
{
for (rem = b % base; i > 0; i--, b /= base, rem = b % base)
{
buf[init + i - 1] = (rem > 9) ? (rem - 10) + 'a' : rem + '0';
}
}
else
{
for (rem = b % base; i > 0; i--, b /= base, rem = b % base)
{
buf[init + i - 1] = (rem > 9) ? (rem - 10) + 'A' : rem + '0';
}
}
}
I got the wrong output I am still getting now. Then I decided to make the conversion to write into the buffer from the current position of the buffer, but I am still getting the same output.
I compiled the program with gcc -g -Werror -Wall -Wextra -Wno-format -pedantic -std=gnu89 *.c
and the output of the program is:
Lowercase hexadecimal:[]
Lowercase hexadecimal:[800003ff]
Uppercase hexadecimal:[]
Uppercase hexadecimal:[800003ff]
Unsigned hexadecimal:[800003ff, ]
Unsigned hexadecimal:[800003ff, 800003FF]
Thank you for your help!
Solution
Thanks to the help of sir Eric Postpischil, I discovered that the issue was caused by a NULL pointer check in the base conversion functions. To resolve the issue, I removed the NULL pointer check from all the base conversion functions since compiling with -Wno-format
will assign NULL as 0 and not raise any errors:
if (!va_arg(args, unsigned int *))
return (-1);
This made the base conversion functions return -1
instead of proceeding to call the convbase function. The NULL check if (!get_fmt(format[idx + 1])(args, buff, &bufpos)
I used in _printf did not catch any errors i.e write the wrong format specifier used, because NULL != -1.
I also changed how the va_list type parameter was passed in all functions, to pass by reference instead of pass by value as was recommended to me by sir Eric Postpischil.
int handle_x(va_list *args, char *buff, int *bufpos)
{
// ...
}
After making these changes, the program started producing the correct output for unsigned hexadecimal values.
Answered By - Najib Muhammad Answer Checked By - Marilyn (WPSolving Volunteer)