【问题标题】:Have some bugs when implementing my own Atoi()实现我自己的 Atoi() 时有一些错误
【发布时间】:2020-05-27 20:56:57
【问题描述】:

我无法理解。当我的函数从 main 中的 char 返回时,随机数。原始 atoi() 返回 -1。我目前使用的是 C11 版本。我从某人那里听说,这是因为 int 溢出,我需要从我的函数中返回 int,但我目前返回的时间很长。如果不是 2147483647,我如何检测 intOverflow

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

bool mx_isdigit(int c) {
    return c >= 48 && c <= 57;
}


bool mx_isspace(char c) {
    return (c >= 9 && c <= 13) || c == 32;
}


int mx_atoi(const char *str) {
    long num = 0;
    int sign = 1;

    for (; mx_isspace(*str); str++);

    if (*str == '-' || *str == '+') {
        sign = *str == '-' ? -sign : sign;
        str++;
    }

    for (; *str; str++) {
        if (!mx_isdigit(*str)) {
            break;
        }
        num = (num * 10) + (*str - '0');
    }
    return sign == -1 ? -num : 0 + num;
}

int main(void) {

    char str[100] = "12327123061232712306";
    printf("R: %d\n", atoi(str));
    printf("M: %d", mx_atoi(str));
}

【问题讨论】:

  • 你可以让sign = -sign。就像 5 = -5
  • 是的,我想我需要检测溢出。但问题是。如果 char 小于 12327123061232712306 的一位数。原始 atoi() 给我一个随机数而不是 -1。
  • 次要注意:使用实际字符而不是数字 ASCII 值是一个非常好的主意;太容易出错了:return c &gt;= '0' &amp;&amp; c &lt;= '9'; 更好的是使用&lt;ctype.h&gt; 宏,它已经有isdigit()isspace() 等。
  • 确实,'0'和'9'很容易使用。但是如果我们想检查空白呢? '\n' '\t' ' ' 等等?
  • '\n' 用于换行,'\t' 用于制表符等。但请查看 ctype 宏,因为它们可能已经包含您需要的内容

标签: c c11 atoi


【解决方案1】:

在您的函数int mx_atoi(const char *str) {... 中,您正在计算long 类型的结果,但该函数返回int;因此,如果存储在num 类型的long 中的结果不适合int,则会丢失一些东西(实际上,由于转换了有符号整数值,因此行为是“实现定义的”,即依赖于编译器)。结果可能会按位截断,产生一个“看起来”与您输入的十进制数完全不同的数字。参见,例如,this 在线 C11 草案。粗体段落适用:

6.3.1.3 有符号和无符号整数

1 当一个整数类型的值被转换为另一个整数类型时 除 _Bool 以外,如果该值可以用新类型表示,则 没有改变。

2 否则,如果新类型是无符号的,则将值转换为 反复加或减一大于最大值 可以用新类型表示,直到值在 新类型.60)

3 否则,新类型有符号,值不能 代表其中;结果是实现定义的或 引发了实现定义的信号。

int mx_atoi(const char *str) 设为long mx_atoi(const char *str),使用long-变量来存储结果,然后不要忘记在printf 中使用格式说明符%ld 而不是%d

否则,如果您需要坚持使用 int 并且想要安全地对溢出做出反应,您可以执行类似的操作

if (num > INT_MAX) {
  return -1;
}

在你的循环中。 INT_MAXlimits.h 中定义

【讨论】:

  • 我想成为 int 的函数。我怎样才能做到这一点?
  • 那么您根本无法返回大于 MAX_INT 的值,并且您需要在函数内部检查 long num 是否大于 MAX_INT 并以某种定义的方式做出反应。
  • 好的,我已将函数更改为长类型。我正在使用clang编译器。但它仍然给我一个随机数而不是-1。哦好的。现在我看到编辑后的答案
  • 那么你仍然可以溢出long;在做num *= 10之前,你需要检查if (num &gt;= (LONG_MAX/10)) { return -1; }
  • long 和在某些情况下long longunsignedint 具有相同的正值范围。要处理所有问题,溢出检测需要依赖int 数学。 if (num &gt;= (LONG_MAX/10)) 这样的代码是不够的。
【解决方案2】:

c >= 48 && c

不要在代码中使用幻数。而不是48 使用'0',它更具可读性并提供您的意图。

我如何检测 intOverflow

当结果大于类型可以表示的最大值时,就会发生溢出。所以有了数字ab 我们可以这样写:

a + b > MAX

但是这样的条件无法检查,因为a + b...会溢出。但是如果我们翻转表达式:

b > MAX - a

可以通过简单的if 轻松检查。 MAX 是一个类型的最大值,对于 int,即 INT_MAX 来自 limits.h

int mx_atoi(const char *str) {    
    for (; mx_isspace(*str); str++);

    bool negative = false;
    if (*str == '-' || *str == '+') {
        negative = *str == '-';
        str++;
    }

    int num = 0;
    for (; mx_isdigit(*str); str++) {
        if (INT_MAX / 10 < num) {
            goto ERR_OVERFLOW;
        }
        num *= 10;
        const unsigned char c = *str - '0';
        if (INT_MAX - c < num) {
            goto ERR_OVERFLOW;
        }
        num += c;

    }
    return negative ? -num : num;
    ERR_OVERFLOW:
    return negative ? INT_MIN : INT_MAX;
}

【讨论】:

  • 在 C11 中,atoi() 不返回 INT_MIN、INT_MAX。
  • @AntonZhukov 不同意“在 C11 中,atoi() 不返回 INT_MIN、INT_MAX。”在 C11、C89、C99、C18 中,使用 32 位 intatoi("‭2147483647‬") 肯定会返回 INT_MAX
  • @KamilCuk 当mx_atoi("‭-2147483648‬") 的转换在int 范围内时,巧妙地使用if (INT_MAX - c &lt; num) { goto ERR_OVERFLOW; 生成INT_MIN。 UV LSNED
  • 顺便说一句:可以使用bool negative = *str == '-'; if (*str == '-' || *str == '+') str++; 进行简化,但现有代码具有指导意义。
【解决方案3】:

int 溢出潜力

num = (num * 10) + (*str - '0'); 遇到int 溢出,这是未定义的行为 (UB),当:

1) 输入字符串应该代表INT_MINint/long 具有相同的范围或
2) 输入字符串对int 范围之外的值进行编码。

避免这种情况的各种方法。

不检测无数字的字符串

在这种情况下返回 0 是合理的,但代码可能需要设置一些错误条件。

不抱怨尾随非数字

简单地忽略尾随字符是合理的,但代码可能需要设置一些错误条件。


避免int 溢出(并且 依赖longint 更宽)的一种方法是在(num * 10) + (*str - '0') 之前进行测试,因为ints 的负数比积极的,积累消极的一面。

bool digit_found = false;
int val = 0;
for (; mx_isdigit(*str); str++) {
    digit_found = true;
    int digit = *str - '\0';
    if (val <= INT_MIN/10 && (val < INT_MIN/10 || digit > -(INT_MIN%10))) { // C99
      return sign == 1 ? INT_MAX : INT_MIN;
    }
    val = val * 10 - digit;  // note subtraction here
}

if (!digit_found) {
    return 0; // Or handle in some other fashion
}

if (sign == 1) {
  // If val is too negative to negate ...
  if (val < -INT_MAX) {
    return INT_MAX;  // overflow
  }
  return -val;
}
return val;

【讨论】:

    【解决方案4】:

    我猜这是最简单的方法。 atoi() 原来使用LLONG_MAX 检查而不是LONG_MAXINT_MAX。所以,试验我发现的那些限制。如果(num * 10) + (*str - '0') 超过long long 类型的限制,它会将数字转换为LLONG_MIN 的负值。所以,我创建了 if 语句,检查下一个计算是否会小于上一个。如果为真,则返回 0 或 -1。

    #include <stdio.h>
    #include <stdbool.h>
    #include <string.h>
    #include <stdlib.h>
    
    bool mx_isdigit(int c);
    bool mx_isspace(char c);
    
    int mx_atoi(const char* str) {
        long long num = 0;
        int sign = 1;
    
        for (; mx_isspace(*str); str++);
    
        if (*str == '-' || *str == '+') {
            sign = *str == '-' ? -sign : sign;
            str++;
        }
    
        for (; *str; str++) {
            if (!mx_isdigit(*str)) {
                break;
            }
    
          if ((num * 10) + (*str - '0') < num) {
              return sign == -1 ? 0 : -1;
          }
    
            num = (num * 10) + (*str - '0');
        }
    
        return sign == -1 ? -num : num;
    }
    
    int main(void) {
    
        char str[100] = "-9223372036854775809";
        printf("R: %d\n", atoi(str));
        printf("M: %d\n", mx_atoi(str));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多