Glibc---_fitoa_word的实现:一个整型数据是如何转成字符串的呢?

时间:2022-12-20 07:13:59

引言

_fitoa_word是一个将int型数据转换为对应进制的char类型的函数,在通常的整型数据处理中非常常见,用来打印一个整型数据对应的N进制表示,对于其原理,实际上就是我们中学时候学习的除数取余法,然后再选取对应的符号进行表示。那么在Glibc源码中,它又是如何实现的呢?接下来,我们一起来看看对应的实现。

函数定义

参考代码:glibc/sysdeps/generic/_itoa.h

/* Similar to the _itoa functions, but output starts at buf and pointer                                                                                                                                     
after the last written character is returned. */
extern char *_fitoa_word (_ITOA_WORD_TYPE value, char *buf,
unsigned int base,
int upper_case) attribute_hidden;

其中:

  • 入参
  • _ITOA_WORD_TYPE value:是本次需要转换的整型量,_ITOA_WORD_TYPE被定义为

  • ​#define _ITOA_WORD_TYPE unsigned long int​​,即64位系统下8个字节大小的整型变量


  • char *buf:本次的输出字符串开始指针
  • unsigned int base:进制
  • int upper_case:标识是否需要大写(如0xf与0XF)
  • 出参
  • char *:标识最后一个写入buf的字符的指针的下一个位置

函数实现

代码位置:glibc/stdio-common/_itoa.c

_fitoa_word逻辑

可以看到,这里的实现实际上还是转交给到了_itoa_word函数,但是在_fitoa_word中也做了如下的处理:

  1. 申请临时buffer tmpbuf,用于_fitoa_word的生成字符串;

注意:这里选择的buffer大小sizeof (value) * 4,考虑到我们进行进制转换时,相同的整型数据,越高的进制生成的字符串越短,所以最坏的情况就是二进制全部用01进行填充,那就应该是有多少位,每个字节8位,所以是sizeof (value) * 8,但这里是*4。

  1. 调用_itoa_word;
  2. 依次将转换后的字符串拷贝到buf中,后置递增运算符是先使用当前值,然后再递增。
char *
_fitoa_word (_ITOA_WORD_TYPE value, char *buf, unsigned int base,
int upper_case)
{
char tmpbuf[sizeof (value) * 4]; /* Worst case length: base 2. */
char *cp = _itoa_word (value, tmpbuf + sizeof (value) * 4, base, upper_case);
while (cp < tmpbuf + sizeof (value) * 4)
*buf++ = *cp++;
return buf;
}

_itoa_word函数参数

与_fitoa_word基本一致,但要注意,这里的char *buflim指向的是分配的临时buffer的最后一个可写位置的下一个地址:tmpbuf + sizeof (value) * 4

extern char *_itoa_word (_ITOA_WORD_TYPE value, char *buflim,                                                                                                                                               
unsigned int base,
int upper_case) attribute_hidden;

_itoa_word函数逻辑

1.根据upper_case选择对应的字符集

根据是否需要大写选择对应的字符集,_itoa_upper_digits 或 _itoa_lower_digits。

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);

// glibc/stdio-common/itoa-udigits.c
/* Upper-case digits. */
const char _itoa_upper_digits[36]
= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

// glibc/stdio-common/itoa-digits.c
/* Lower-case digits. */
const char _itoa_lower_digits[36]
= "0123456789abcdefghijklmnopqrstuvwxyz";

2.使用除数取余法进行进制转换

注意,这里定义了一个宏SPECIAL(Base),针对我们常用的10进制、16进制、8进制做了简化,但实际逻辑还是与default的一致。

  • 先对value使用%取余数,得到对应的符号;
  • 注意--buflim是先做递减运算,再使用该值,第一次递减则刚好是上面tmpbuf的最后一个位置;
  • 使用*取地址并进行赋值;
  • 对value做除法,直到除数为0,否则重复上面的步骤。

上面就是一个标准的除数取余法。

switch (base)
{
#define SPECIAL(Base) \
case Base: \
do \
*--buflim = digits[value % Base]; \
while ((value /= Base) != 0); \
break

SPECIAL (10);
SPECIAL (16);
SPECIAL (8);
default:
do
*--buflim = digits[value % base];
while ((value /= base) != 0);
}

3.返回buflim指针

注意,这里返回的buflim实际上最后指向的是第一个字符;

然后undef SPECIAL,避免后续使用出现覆盖异常

return buflim;
}
#undef SPECIAL

总结

_fitoa_word函数是Glibc中的函数,将一个整型数据转换为对应进制的字符串数据,主要是调用了除数取余法实现。