素槿
Published on 2025-08-22 / 3 Visits
0

atoi函数的问题及解决方法

在最近的一个项目中需要对传入的字符串参数转换成 int类型,并对转换后的值进行判断,看是否在 0-7内,最直接的办法就是调用函数:

#include <stdlib.h>
int atoi(const char *nptr)

但是这个函数有一个明显的问题,对于非法的输入,返回值都为零,而且不进行错误检查。
这就造成当函数返回零的时候无法判断输入的是字符 0还是非法字符。

如下面的输入和对应的输出:

atoi("aaa") = 0
atoi("0")   = 0
atoi("123abc") = 123
atoi("abc123") = 0

第一行和第二行输出一样但是输入不一样,造成程序无法区别非法输入和 0, 第三行在我们的需求中属于非法输入,但却切断字符返回正常整数。这就造成如果输入的是 3a,返回值则为 3,和我们期望的返回不一致。

下面给出针对这个项目需求的改进方法。


使用atoi转换前进行检查

既然atoi函数本身的功能不能满足我们的要求,那我们就自己进行检查。

在下面的代码中,我们用返回值来指示传入的字符是否非法,如果非法,返回1,否则返回零。字符串的转换结果使用函数参数返回。

转换函数在第3行检测空指针和空字符;第6行的循环跳过所有前面的空格;第8行检测是否有正负号;第10行的循环遍历剩下的字符,如果有非数字字符,则返回1。否则调用atoi函数进行转换。现在这个函数可以满足我们项目的全部要求。

但是,这个函数的代码比较比较复杂,各种检查不容易理解和维护。进一步的改进请往下看。

int atoi_c(const char* buf, int* result)
{
    int offset = 0;
    if (buf == NULL || *buf == '\0') 
        return 1;
    while(buf[offset] == ' ')
        offset++;
    if(buf[offset] == '+' || buf[offset] == '-')
        offset++;
    while (buf[offset] != '\0')
    {
        if(buf[offset] > '9' || buf[offset] < '0'){
            return 1;
        }  
        offset++;
    }

    *result = atoi(buf);
    return 0;
}

使用strtol检查非法字符

通过查阅man手册,发现 strtol函数可以通过对参数进行检查来判断输入的字符是否是合法的字符。

#include <stdlib.h>
long int strtol(const char *nptr, char **endptr, int base);

第一个参数就是要转换的字符串;第二个参数非 NULL时,储存第一个非法的转换字符;第三个参数是转换的进制。有了第二个参数我们可以通过检查它是不是字符串的最后一个字符(\0)来判断整个字符串是不是合法的。

改进之后的代码如下所示:

int atoi_c(const char* buf, int* result)
{
    char* invalid;
    *result = (int)strtol(buf, &invalid, 10);
    if(*invalid != '\0')
        return 1;
    return 0;
}

但是这个代码还是有一点问题,当我们将 long int类型的值强制转换成 int时,就有可能发生溢出,导致最后的转换结果出错。而且我们只需要对 0-7的值进行判定,所以使用 unsigned int足够了。

我们继续对代码进行改进。


使用strtoumax改进代码

#include <inttypes.h>
intmax_t strtoimax(const char *nptr, char **endptr, int base);

在上面的函数原型中,各个参数的作用和 strtol的一样但是这个函数有控制溢出的能力,更多的信息可以查看 man 3 strtoumax

函数的返回值 intmax_tUbuntu2004 中的定义是 unsigned long int,为了适应返回值,我们可以对返回值先进行比较,如果大于 unsigned int的最大值的话,返回错误。最后的代码如下:

#include<inttypes.h>
int atoi_c(const char* buf, unsigned int* result)
{
    if (buf == NULL || *buf == '\0') 
        return 1;
  
    char* invalid;
    uintmax_t ret;
    ret = strtoumax(buf, &invalid, 10);
    if(*invalid != '\0')
        return 1;
    if(ret> UINT32_MAX)
        return 1;
    *result = (unsigned int)ret;
    return 0;
}