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!
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-formatwill assign NULL as 0 and not raise any errors:This made the base conversion functions
return -1instead of proceeding to call the convbase function. The NULL checkif (!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.
After making these changes, the program started producing the correct output for unsigned hexadecimal values.