Lance Pollard
Lance Pollard

Reputation: 79360

Understanding how sprintf converts integers to strings

I am in the process of learning C and on a basic example of converting integers to string (which you take for granted in other languages). The answer I found was this:

sprintf(str, "%d", num);

But I would like to see how this is implemented. So I look up sprintf and it takes me here:

#define vsprintf(s, f, a) _IO_vsprintf (s, f, a)

int
__sprintf (char *s, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = vsprintf (s, format, arg);
  va_end (arg);

  return done;
}

So then I look up _IO_vsprintf and it shows:

int
__IO_vsprintf (char *string, const char *format, va_list args)
{
  _IO_strfile sf;
  int ret;

#ifdef _IO_MTSAFE_IO
  sf._sbf._f._lock = NULL;
#endif
  _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
  _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
  _IO_str_init_static_internal (&sf, string, -1, string);
  ret = _IO_vfprintf (&sf._sbf._f, format, args);
  _IO_putc_unlocked ('\0', &sf._sbf._f);
  return ret;
}

Which seems to revolve around _IO_vfprintf. But I can't find where that is in the code. Google and GitHub bring no results. Wondering if anyone has the full implementation of sprintf available or if not, can explain how it works.

I would like to know how to implement itoa as sprintf does it, which sounds like is a good implementation.

I am confused as to how this code can actually run if there is no implementation.

Upvotes: 1

Views: 269

Answers (1)

KamilCuk
KamilCuk

Reputation: 141483

Looks like _IO_vfprintf is a hidden alias to _IO_vfprintf_internal.

When compiling the file vfprintf.c file for normal (not wide) characters, the fprintf becomes a macro defined as _IO_vfprintf_internal, from here and undefined before aliasing here.

So _IO_vfprintf_internal declaration starts here, where vfprintf is declared (well, "in un-proprocessed source file", the identifier vfprintf get's never declared).

After the vfprintf functions stopped processing all the width, spaces, minuses and other format specifiers, it finally will jump through jump tables (that basically is a smart way of jumping around to different goto labels by a jump table with addresses obtained through GCC extensions labels as values) it will land on form_integer: label. From there we jump to number:. From there we call _itoa_word.

Now _itoa_word is plainly simple and easy, even enough to post here the preprocessed function:

char *
_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
        unsigned int base, int upper_case) {
        const char *digits = (upper_case
               ? _itoa_upper_digits
               : _itoa_lower_digits);

      ...
      switch (base)
      {
      ...
      // SPECIAL (10); expands to:
      case 10:
          do
              *--buflim = digits[value % Base];
          while ((value /= Base) != 0);
          break;
      ...
      }
      ...
}

Where the digits, as in _itoa_upper_digits or as in _itoa_lower_digits is a simple lookup table, a char array initialized from a string literal, used to convert decimals to their associated ascii representation.

After _itoa_word there some code for handling left justifing the string, printing the leading sign character and padding the string with spaces or zeros, but finally our string gets outputted (or left justified outputted).

example of converting integers to string

So 99% of the work is for handling all the strange

printf("%+- 3lld %+- 4.3Lf %-+04hhd %+- 5zd", 1llu, 2.L, 3, (ssize_t)4);

format specifiers it needs to conform to. The conversion is just a simple conversion using a lookup string (to handle non-ASCII systems) that work's it's way backwards through the number:

#include <stdio.h>

char *my_itoa(char *dest, size_t destlen, int number)
{
    // we will work our way from the ending
    char *p = &dest[destlen];
    // null terminate the string
    *--p = '\0';

    while (number) {
        if (p == dest) {
            // destination buffer too small!!
            return NULL;
        }
        *--p = "0123456789"[number % 10];
        number /= 10;
    }

    return p;
}

int main()
{
    char buf[20];
    printf("%s\n", my_itoa(buf, sizeof(buf), 123));
}

Upvotes: 2

Related Questions