Tuesday, February 6, 2024

[SOLVED] Incorrect output for unsigned hexadecimal values in custom _printf function in C

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)