免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5505 | 回复: 3
打印 上一主题 下一主题

[应用] Linux源码中的number(将整数转按指定的进制转换成字符串)函数的实现 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-06-26 00:42 |只看该作者 |倒序浏览
本帖最后由 machunleilei 于 2011-06-26 00:45 编辑

首先给出函数的申明,好有一个整体的把握。
static noinline_for_stack
char *number(char *buf, char *end, unsigned long long num,
             struct printf_spec spec)。
先说一下,关于结构printf_spec的定义:
struct printf_spec {
        u8        type;                /* format_type enum */
        u8        flags;                /* flags to number() */
        u8        base;                /* number base, 8, 10 or 16 only */
        u8        qualifier;        /* number qualifier, one of 'hHlLtzZ' */
        s16        field_width;        /* width of output field */
        s16        precision;        /* # of digits/chars */
};
其中 u8是unsigned char。s16就是signed int
从上面的英文解释中,我们就能理解,成员变量type主要就是关于规格化类型使用的,而flag主要是一些定义的宏,在后面会介绍。base就是保存我们需要将整数转换成的数制。qualifier在该函数中没有使用,我也没有具体研究。field_width就是我们需要得到转换之后的数值的宽度,用过printf这个函数的同学应该都知道我们在用printf约束输出整数的宽度的时候,如果整数的实际宽度大于我们规定的宽度,那么也会将整个整数输出而不会发生截断,当然如果整数的宽度小于我们需要的输出宽度就会填充字符,具体填充什么东西我们可以自己指定,这个变量的作用跟我们使用printf中规定输出宽度一样。变量precision作用就是体现在转换之后的字符窜宽度要大于我们规定的宽度的时候,取大者,不进行截断。

现在还需要介绍几个宏的定义:
#define ZEROPAD        1                /* pad with zero */
#define SIGN        2                /* unsigned/signed long */
#define PLUS        4                /* show plus */
#define SPACE        8                /* space if plus */
#define LEFT        16                /* left justified */
#define SMALL        32                /* use lowercase in hex (must be 32 == 0x20) */
#define SPECIAL        64                /* prefix hex with "0x", octal with "0" */
相信大家都能看懂上面的英文解释,在这里只简单的提一下
ZEROPAD用0来填充,SIGN我们需要注意整数的符号,PLUS显示+号,SPACE可以用‘ ’代替+,LEFT左边对齐,SMALL小写,也就是0~f表示,SPECIAL需要前缀。

说了这么多,现在让我们具体来看一下这个函数的实现细节:
static noinline_for_stack
char *number(char *buf, char *end, unsigned long long num,
             struct printf_spec spec)
{
        /* we are called with base 8, 10 or 16, only, thus don't need "G..."  */
        static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ";这个数组主要的作用
           是方便我们在进行数值转换的时候,将整数变成字符类型 */
        char tmp[66];/*这个主要用来临时保存将整数转换成的指定的进制的字符串*/        char sign;
        char locase;
        int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);
               /*判断首先我们规定了没有需要注意前缀,如果我们不需要前缀,那么当然就不需要了,同时也要满足不是10进制的*/        int i;

        /* locase = 0 or 0x20. ORing digits or letters with 'locase'
         * produces same digits or (maybe lowercased) letters */
        locase = (spec.flags & SMALL)/*判断是不是需要转换成小写的表示。*/        if (spec.flags & LEFT)/*判断是不是需要左对齐*/                spec.flags &= ~ZEROPAD;
        sign = 0;
        if (spec.flags & SIGN) {/*我们需不需要注意数值的正负,当然如果需要,就要判断是正数还是负数,具体怎样表示下面说的很清楚*/                if ((signed long long)num < 0) {
                        sign = '-';
                        num = -(signed long long)num;
                        spec.field_width--;
                } else if (spec.flags & PLUS) {
                        sign = '+';
                        spec.field_width--;
                } else if (spec.flags & SPACE) {//我们是否规定了用空格来代替+号了                        sign = ' ';
                        spec.field_width--;
                }
        }
/*每一个分支中,我们都可以看出来spec.filed_width自减了,这是因为符号也占据了我们规定的长度。*/        if (need_pfx) { /*判断需不需要添加前缀,就是0x或者0.*/                spec.field_width--; /*不论是16进制或者是8进制,都会将宽度--,因为0x或者0的最下宽度是1.0x还需要-- */                if (spec.base == 16)
                        spec.field_width--;
        }

        /* generate full string in tmp[], in reverse order */
        i = 0;
        if (num == 0)
                tmp[i++] = '0';
        /* Generic code, for any base:
        else do {
                tmp[i++] = (digits[do_div(num,base)] | locase);
        } while (num != 0);
        */
        else if (spec.base != 10) { /* 8 or 16 */
                int mask = spec.base - 1;
                int shift = 3;

                if (spec.base == 16)
                        shift = 4;
                do {
                        tmp[i++] = (digits[((unsigned char)num) & mask] | locase);
                        num >>= shift;
                } while (num);
        } else { /* base 10 */
                i = put_dec(tmp, num) - tmp;
        }/*上面的这段代码就是将数值转换成了字符串了,具体很容易理解。*/

        /* printing 100 using %2d gives "100", not "00" */
        if (i > spec.precision)
                spec.precision = i;
        /* leading space padding */
        spec.field_width -= spec.precision;
        if (!(spec.flags & (ZEROPAD+LEFT))) {
                while (--spec.field_width >= 0) {
                        if (buf < end)
                                *buf = ' ';
                        ++buf;
                }
        }/*判断数值转换成为了字符串之后长度如果超过了我们规定的长度,则不进行截断,同时要将precision修改成为实际的长度*/        /* sign */
        if (sign) {
                if (buf < end)
                        *buf = sign;
                ++buf;
        } /*加入符号字符*/        /* "0x" / "0" prefix */
        if (need_pfx) {
                if (buf < end)
                        *buf = '0';
                ++buf;
                if (spec.base == 16) {
                        if (buf < end)
                                *buf = ('X' | locase);
                        ++buf;
                }
        }
        /* zero or space padding */
        if (!(spec.flags & LEFT)) {
                char c = (spec.flags & ZEROPAD) ? '0' : ' ';
                while (--spec.field_width >= 0) {
                        if (buf < end)
                                *buf = c;
                        ++buf;
                }
        }
        /* hmm even more zero padding? */
        while (i <= --spec.precision) {
                if (buf < end)
                        *buf = '0';
                ++buf;
        }  /*填充0*/
        /* actual digits of result */
        while (--i >= 0) {
                if (buf < end)
                        *buf = tmp;
                ++buf;
        }
        /* trailing space padding */
        while (--spec.field_width >= 0) {
                if (buf < end)
                        *buf = ' ';
                ++buf;
        }

        return buf;
}

论坛徽章:
0
2 [报告]
发表于 2011-06-30 23:12 |只看该作者
Linux源码是最好的学习材料了,以你这钟钻研精神,提高一定很快的。

论坛徽章:
0
3 [报告]
发表于 2011-07-18 15:10 |只看该作者
解读的时候很慢,但是可以从里面学到不少的好东西呢

论坛徽章:
0
4 [报告]
发表于 2011-07-18 15:11 |只看该作者
回复 2# guanxw6317
解读过程很慢,但是的确可以学到很多好的东西
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP