字符串转换为整数

最近,突然兴致突发,开始玩CSDN的pango系统里面的类似Online Judge的测评系统。刷了3道水题,错了一道,时之今日才知道错误的原因,参考http://blog.csdn.net/v_july_v/article/details/9024123#commentsJuly大神的这篇文章把这道题来分析一下。

题目:

当给的字符串是如左边图片所示的时候,有考虑到么?当然,它们各自对应的正确输出如右边图片所示( 假定你是在32位系统下,且编译环境是VS2008以上):
刚拿到题时,看都没看,心想不就是找整数嘛,一个栈搞定,最终没有仔细审题,没有注意到数据范围的问题。(这道题最大的陷阱就在于有数据范围限值)

题上说道字符串转INT,大家都知道int类型是32位的有符号类型,大小是-2^31 ~ (2 ^ 31 - 1)。


题上很清晰的提到,不能使用Microsoft的atoi函数,那我们不妨来看看微软的atoi函数是如何来实现的。

  1. //atol函数  
  2. //Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.  
  3. long __cdecl atol(  
  4.     const char *nptr  
  5.     )  
  6. {  
  7.     int c; /* current char */  
  8.     long total; /* current total */  
  9.     int sign; /* if ''-'', then negative, otherwise positive */  
  10.   
  11.     /* skip whitespace */  
  12.     while ( isspace((int)(unsigned char)*nptr) )  
  13.         ++nptr;  
  14.   
  15.     c = (int)(unsigned char)*nptr++;  
  16.     sign = c; /* save sign indication */  
  17.     if (c == ''-'' || c == ''+'')  
  18.         c = (int)(unsigned char)*nptr++; /* skip sign */  
  19.   
  20.     total = 0;  
  21.   
  22.     while (isdigit(c)) {  
  23.         total = 10 * total + (c - ''0''); /* accumulate digit */  
  24.         c = (int)(unsigned char)*nptr++; /* get next char */  
  25.     }  
  26.   
  27.     if (sign == ''-'')  
  28.         return -total;  
  29.     else  
  30.         return total; /* return result, negated if necessary */  
  31. }  
    其中,isspace和isdigit函数的实现代码为:
  1. isspace(int x)    
  2. {    
  3.     if(x==' '||x=='/t'||x=='/n'||x=='/f'||x=='/b'||x=='/r')    
  4.         return 1;    
  5.     else     
  6.         return 0;    
  7. }    
  8.   
  9. isdigit(int x)    
  10. {    
  11.     if(x<='9'&&x>='0')             
  12.         return 1;     
  13.     else     
  14.         return 0;    
  15. }   
    然后 atoi调用上面的atol函数,如下所示:
  1. //atoi调用上述的atol  
  2. int __cdecl atoi(  
  3.     const char *nptr  
  4.     )  
  5. {  
  6.     //Overflow is not detected. Because of this, we can just use  
  7.     return (int)atol(nptr);  
  8. }  

    但很遗憾的是,上述atoi标准代码依然返回的是long:

  1. long total; /* current total */  
  2. if (sign == ''-'')  
  3.     return -total;  
  4. else  
  5.     return total; /* return result, negated if necessary */  

    再者,下面这里定义成long的total与10相乘,即total*10很容易溢出:

  1. long total; /* current total */  
  2. total = 10 * total + (c - ''0''); /* accumulate digit */  
从上看出,貌似微软api也有bug,当然这段代码是否是真实的微软api源码,还有待考究。

接下来,咱们来看看 linux内核中是如何实现此字符串转换为整数的问题的。
linux内核中提供了以下几个函数:
  1. simple_strtol,把一个字符串转换为一个有符号长整数;
  2. simple_strtoll,把一个字符串转换为一个有符号长长整数;
  3. simple_strtoul,把一个字符串转换为一个无符号长整数;
  4. simple_strtoull,把一个字符串转换为一个无符号长长整数
    相关源码及分析如下。
    首先,atoi调下面的strtol:
  1. //linux/lib/vsprintf.c  
  2. //Copyright (C) 1991, 1992  Linus Torvalds  
  3. //simple_strtol - convert a string to a signed long  
  4. long simple_strtol(const char *cp, char **endp, unsigned int base)  
  5. {  
  6.     if (*cp == '-')  
  7.         return -simple_strtoul(cp + 1, endp, base);  
  8.   
  9.     return simple_strtoul(cp, endp, base);  
  10. }  
  11. EXPORT_SYMBOL(simple_strtol);  
    然后,上面的strtol调下面的strtoul:
  1. //simple_strtoul - convert a string to an unsigned long  
  2. unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base)  
  3. {  
  4.     return simple_strtoull(cp, endp, base);  
  5. }  
  6. EXPORT_SYMBOL(simple_strtoul);  
    接着,上面的strtoul调下面的strtoull:
  1. //simple_strtoll - convert a string to a signed long long  
  2. long long simple_strtoll(const char *cp, char **endp, unsigned int base)  
  3. {  
  4.     if (*cp == '-')  
  5.         return -simple_strtoull(cp + 1, endp, base);  
  6.   
  7.     return simple_strtoull(cp, endp, base);  
  8. }  
  9. EXPORT_SYMBOL(simple_strtoll);  
    最后,strtoull调_parse_integer_fixup_radix和_parse_integer来处理相关逻辑:
  1. //simple_strtoull - convert a string to an unsigned long long  
  2. unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)  
  3. {  
  4.     unsigned long long result;  
  5.     unsigned int rv;  
  6.   
  7.     cp = _parse_integer_fixup_radix(cp, &base);  
  8.     rv = _parse_integer(cp, base, &result);  
  9.     /* FIXME */  
  10.     cp += (rv & ~KSTRTOX_OVERFLOW);  
  11.   
  12.     if (endp)  
  13.         *endp = (char *)cp;  
  14.   
  15.     return result;  
  16. }  
  17. EXPORT_SYMBOL(simple_strtoull);  
    重头戏来了。接下来,我们来看上面strtoull函数中的parse_integer_fixup_radix和_parse_integer两段代码。如鲨鱼所说
  • “真正的处理逻辑主要是在_parse_integer里面,关于溢出的处理,_parse_integer处理的很优美,
  • 而_parse_integer_fixup_radix是用来自动根据字符串判断进制的”。
    先来看 _parse_integer函数:
  1. //lib/kstrtox.c, line 39    
  2. //Convert non-negative integer string representation in explicitly given radix to an integer.    
  3. //Return number of characters consumed maybe or-ed with overflow bit.    
  4. //If overflow occurs, result integer (incorrect) is still returned.    
  5. unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)    
  6. {    
  7.     unsigned long long res;    
  8.     unsigned int rv;    
  9.     int overflow;    
  10.     
  11.     res = 0;    
  12.     rv = 0;    
  13.     overflow = 0;    
  14.     while (*s) {    
  15.         unsigned int val;    
  16.     
  17.         if ('0' <= *s && *s <= '9')    
  18.             val = *s - '0';    
  19.         else if ('a' <= _tolower(*s) && _tolower(*s) <= 'f')    
  20.             val = _tolower(*s) - 'a' + 10;    
  21.         else    
  22.             break;    
  23.     
  24.         if (val >= base)    
  25.             break;    
  26.         /*  
  27.          * Check for overflow only if we are within range of  
  28.          * it in the max base we support (16)  
  29.          */    
  30.         if (unlikely(res & (~0ull << 60))) {    
  31.             if (res > div_u64(ULLONG_MAX - val, base))    
  32.                 overflow = 1;    
  33.         }    
  34.         res = res * base + val;    
  35.         rv++;    
  36.         s++;    
  37.     }    
  38.     *p = res;    
  39.     if (overflow)    
  40.         rv |= KSTRTOX_OVERFLOW;    
  41.     return rv;    
  42. }  
    解释下两个小细节:
  1. 上头出现了个unlikely,其实unlikely和likely经常出现在linux相关内核源码中
    1. if(likely(value)){  
    2.     //等价于if(likely(value)) == if(value)  
    3. }  
    4. else{  
    5. }  
    likely表示value为真的可能性更大,而unlikely表示value为假的可能性更大,这两个宏被定义成:
    1. //include/linux/compiler.h  
    2. # ifndef likely  
    3. #  define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))  
    4. # endif  
    5. # ifndef unlikely  
    6. #  define unlikely(x)   (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))  
    7. # endif  
  2. 呈现下div_u64的代码:

  1. //include/linux/math64.h  
  2. //div_u64  
  3. static inline u64 div_u64(u64 dividend, u32 divisor)  
  4. {  
  5.     u32 remainder;  
  6.     return div_u64_rem(dividend, divisor, &remainder);  
  7. }  
  8.   
  9. //div_u64_rem  
  10. static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder)  
  11. {  
  12.     *remainder = dividend % divisor;  
  13.     return dividend / divisor;  
  14. }  

    最后看下_parse_integer_fixup_radix函数:
  1. //lib/kstrtox.c, line 23  
  2. const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)  
  3. {  
  4.     if (*base == 0) {  
  5.         if (s[0] == '0') {  
  6.             if (_tolower(s[1]) == 'x' && isxdigit(s[2]))  
  7.                 *base = 16;  
  8.             else  
  9.                 *base = 8;  
  10.         } else  
  11.             *base = 10;  
  12.     }  
  13.     if (*base == 16 && s[0] == '0' && _tolower(s[1]) == 'x')  
  14.         s += 2;  
  15.     return s;  
  16. }  
看看源码中的实现,总是不免感慨,代码清晰不说,功能模块分的太详细了。简直就是工艺品,难怪很多人都喜欢去研究源码了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值