2

I am trying to not only return the output to the stdout but also trying to return the size of each data type that would be tested. I am using the write() syscall to write the simple output to the stdout (and yes I am sticking to write() since it is more efficient space + time complexity wise) on my else statement but I'm not sure about the logic of each of the case clauses for each variable conversion. Also any feedback on my code organization would also be greatly appreciated as well as any feedback at all whatsoever.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdint.h>

char *convert(unsigned int, int);

int my_printf(char *format, ...)
{
    va_list args;
    va_start(args, format);
    unsigned int i;
    char *input;
    char *s;
    void *p;
    
    for (input = format; *input != '\0'; input++)
    {
        if (*input == '%') {
            input++;
            //FETCH & EXECUTE VARIABLE CONVERSION
            switch (*input)
            {
                case '%':   write(1, "%", 1);
                            break;

                case 'd':   i = va_arg(args, int);
                            if (i <= 0)
                            {
                                i = -i;
                                write(1, "-", 1);
                            }
                            write(1, convert(i, 10), strlen(convert(i, 10)));
                            break;
                
                case 'o':   i = va_arg(args, unsigned int);
                            write(1, convert(i, 8), strlen(convert(i, 8)));
                            break;
             

                case 'u':   i = va_arg(args, unsigned int);
                            write(1, convert(i, 10), strlen(convert(i, 10)));
                            break;
                

                case 'x':   i = va_arg(args, unsigned int);
                            write(1, convert(i, 16), strlen(convert(i, 16)));
                            break;
                

                case 'c':   i = va_arg(args, int);
                            write(1, &i, 1);
                            break;

                case 's':   s = va_arg(args, char *);
                            write(1, s, strlen(s));
                            break;

                case 'p':   p = va_arg(args, void*);
                            intptr_t ptr_val = (intptr_t)p;
                            write(1, convert(ptr_val, 16), strlen(convert(ptr_val, 16)));
                            break;

                default:    write(1, input - 1, 2);
                            break;
            }
        }
        else 
        {
                            write(1, input, 1);
        }
    }
    va_end(args);
    return 0;
}

char *convert(unsigned int n, int base)
{
    static char Rep[] = "0123456789ABCDEF";
    static char buffer[50];
    char *ptr;

    ptr = &buffer[49];
    *ptr = '\0';

    do
    {
        *--ptr = Rep[n % base];
        n /= base;
    }
    while (n != 0);

    return (ptr);
}

I tried using strlen() inside of write() for each case but I am not getting back a data byte size value, which I thought could work with strlen(). I also tried size_t and sizeof in place of strlen() which did not work.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
danisvicex
  • 21
  • 3

2 Answers2

1

[I am] also trying to return the size of each data type that would be tested

It is unclear what you mean, but you should return the total number of characters output to stdout.

Note that using the write system call directly for each character and conversion is probably less efficient than using putchar() and taking advantage of the buffering performed by the standard stream functions.

There are some problems in your code:

  • for the format string "%": you output a % and a null byte and continue iterating beyond the end of the string, invoking undefined behavior.

  • converting the numbers twice is inefficient. You should store the pointer returned by convert and use that as an argument to write and strlen. Alternately, convert could return the number of characters produced to avoid the need for strlen().

  • the support for negative numbers is incomplete: INT_MIN will invoke undefined behavior because computing -i causes an arithmetic overflow. It produces a negative number for which most digits computed by convert will be negative, invoking undefined behavior when indexing Rep[n % base] with a negative value.

  • unsigned conversions %u and %o are incorrect for large values that will be received as negative numbers by convert.

  • format %c does not work on big endian architectures. You should store the byte value into a char variable and pass its to write.

  • format %p does not work if intptr_t is a larger type than int and does not handle large pointer values (negative argument issue).

Here is a modified version:

#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int convert(char *dest, unsigned long long n, int base, const char *digits) {
    char buffer[sizeof(n) * CHAR_BIT];
    char *ptr = buffer;
    int len = 0;

    do {
        *ptr++ = digits[n % base];
        n /= base;
    } while (n != 0);

    while (ptr > buffer) {
        dest[len++] = *--ptr;
    }
    dest[len] = '\0';
    return len;
}

int my_printf(const char *format, ...) {
    static const char rep_lower[] = "0123456789abcdef";
    static const char rep_upper[] = "0123456789ABCDEF";
    unsigned long long n;
    char buffer[sizeof(n) * CHAR_BIT + 3];
    va_list args;
    int i, len;
    const char *input, *last;
    const char *s;
    char *r;
    void *p;
    int res = 0;

    va_start(args, format);
    for (input = last = format;; input++) {
        /* scan for a conversion specification */
        if (*input != '\0' && (*input != '%' || input[1] == '\0'))
            continue;
        if (input > last) {
            /* output pending format string fragment */
            res += write(1, last, input - last);
        }
        if (*input == '\0')
            break;
        input++;
        s = r = buffer;
        switch (*input) {
        case '%': *r = '%';
            len = 1;
            break;

        case 'd': n = i = va_arg(args, int);
            if (i < 0) {
                *r++ = '-';
                n = -n;
            }
            len = convert(r, n, 10, rep_lower) + (r - s);
            break;

        case 'u': n = va_arg(args, unsigned int);
            len = convert(r, n, 10, rep_lower);
            break;

        case 'b': n = va_arg(args, unsigned int);
            len = convert(r, n, 2, rep_lower);
            break;

        case 'o': n = va_arg(args, unsigned int);
            len = convert(r, n, 8, rep_lower);
            break;

        case 'x': n = va_arg(args, unsigned int);
            len = convert(r, n, 16, rep_lower);
            break;

        case 'X': n = va_arg(args, unsigned int);
            len = convert(r, n, 16, rep_upper);
            break;

        case 'c': *r = (char)va_arg(args, int);
            len = 1;
            break;

        case 's': s = va_arg(args, char *);
            if (s == NULL) {
                s = "(null)";
            }
            len = strlen(s);
            break;

        case 'p': p = va_arg(args, void *);
            n = (unsigned long long)(intptr_t)p;
            *r++ = '0';
            *r++ = 'x';
            len = convert(r, n, 16, rep_upper) + (r - s);
            break;

        default: s = input - 1;
            len = 2;
            break;
        }
        last = input + 1;
        res += write(1, s, len);
    }
    va_end(args);
    return res;
}

int main(void) {
    int i = -123;
    int u = 123456;
    int res = 0;
    res += my_printf("Hello %s! %d%%\n", "world", 100);
    res += my_printf("%u = binary %b = octal %o = hex %x = %X\n", u, u, u, u, u);
    res += my_printf("-123 = %d\n", i);
    res += my_printf("'%c' = %d\n", 'a', 'a');
    res += my_printf("&u = %p\n", (void *)&u);
    my_printf("%d bytes\n", res);
    return 0;
}

Output:

Hello world! 100%
123456 = binary 11110001001000000 = octal 361100 = hex 1e240 = 1E240
-123 = -123
'a' = 97
&u = 0x16B07B32C
125 bytes
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

Use the sizeof() operator to determine the size of each variable type if you wish to return the size of each data type that is being tested in your code. To find the size of an integer, for instance, use sizeof(int). To retrieve the length of the output string, use strlen() however, if you wish to return the size of the output string for each conversion, you must also include the size of any additional characters you are writing to stdout, such as a newline or a space. Consider breaking up the conversion logic into distinct functions to better structure your code and make it easier to read and maintain.

user16217248
  • 3,119
  • 19
  • 19
  • 37