【问题标题】:Recoding itoa: error on minimum int重新编码 itoa:最小 int 错误
【发布时间】:2016-03-14 01:18:57
【问题描述】:

我正在尝试重新编码 itoa 函数,给定一个 int,它将返回一个表示其十进制值的字符串。到目前为止,这些功能运行良好:

char        *ft_itoa(int n)
{
    char    s[1024];
    int     i;
    int     neg;

    i = 0;
    neg = 0;
    if (n == 0)
        s[i++] = '0';
    if (n < 0)
    {
        n = n * (-1);
        neg = 1;
    }
    while (n != 0)
    {
        s[i++] = (n % 10) + 48;
        n /= 10;
    }
    if (neg)
        s[i++] = '-';
    s[i] = '\0';
    return (ft_strrev(s));
}

除了最小的 int 值,-2147483648。在这种情况下,函数返回:

"-./,),(-*,("

威奇……很奇怪。请注意, ft_strrev 将反转结果并对其进行 malloc。有什么线索吗?

编辑:

这里有很多有趣的答案。我对缓冲区的最小尺寸特别感兴趣。使用limits.h 似乎可以解决问题,但我不允许 包含除stdlib.h 和string.h 之外的其他标头。我也受限于三个函数,malloc、free 和 write。但是,我确实从 libc 中重新编码了 strdup 和许多函数。

有人可以解释为什么那行会声明我需要的确切内存量:

char   buf[sizeof(int) * CHAR_BIT / 3 + 3];

还有,

使用无符号来计算数字可以避免 INT_MIN 的问题。 INT_MIN 的错误修复。

为什么?

【问题讨论】:

  • 请发MVCE
  • -2147483648 * -1 在 32 位整数中溢出。
  • @BLUEPIXY 两个标志的限制不应该相同吗?我不明白。
  • 32bit int 最大值为2147483647
  • 0 也包含在数字的总范围内,因此正符号数会少一个数字。

标签: c


【解决方案1】:

您的代码有几个小问题:

  • 缓冲区太大:包括符号和空终止符,24 字节就足够了。对于绝对可移植性,sizeof(int)*CHAR_BIT/3 + 3 的上限是正确的。 不是错误,而是浪费

  • 如果将数字从右到左存储到缓冲区中,则不需要最后的反向阶段,可以直接调用strdup()更简单更快

  • 使用unsigned 计算数字可以避免 INT_MIN 的问题。 修复了 INT_MIN 的错误

  • 循环查找i &gt;= 10 并单独存储最后一位数字可避免0 的特殊情况。 更简单,更快,更少的部门

  • 您应该使用'0' 而不是硬编码ASCII 值48更具可读性和便携性

这是修改后的版本:

#include <limits.h>

char *ft_itoa(int n) {
    char buf[sizeof(int)*CHAR_BIT/3 + 3];
    char *s;
    unsigned int v;

    v = n;
    if (n < 0) {
        v = -v;
    }
    s = buf + sizeof(buf);
    *--s = '\0';
    while (v >= 10) {
        *--s = '0' + v % 10;
        v /= 10;
    }
    *--s = '0' + v;
    if (n < 0)
        *--s = '-';
    return strdup(s);
}

如果您的系统上没有strdup,如果您从堆中分配字符串,它很容易实现并且非常有用。

【讨论】:

  • 角落案例:使用unsigned,通常可以应付INT_MIN。然而 C 并没有指定UINT_MAX &gt; INT_MAX。奇数球平台对int 使用符号位,并且UINT_MAX == INT_MAX-INT_MIN 不能表示为unsigned
  • @chux:是的,但这些天是一个非常理论的观点。我知道的所有具有UINT_MAX == INT_MAX 的奇数球平台也使用符号/大小表示,因此-INT_MIN 可以表示为unsigned。我很想看看和操作你的老式 DS9K 以获得乐趣;-)
  • @chux:我期待一个更笼统的评论,例如:你假设 2 的补码。实际上v = -v 避免了整数溢出,但可能不会在(假设的)非 2 补码硬件上产生预期值。
  • 在查看具有高度可移植性的源代码时,遇到了 int 的负面而非正面的求和技术。有用的属性是,由于它根本不使用unsigned,因此与int 相比,该类型的属性没有实际意义。有些人发现它更难遵循,但它确实有效
  • 2^63 和这个有什么关系?
【解决方案2】:

避免int 溢出(if (n &lt; 0) { n = n * (-1); 导致 OP 的问题)并接受黑暗(消极)的一面。由于负数int 比正数多,因此在负数上求和。

#include <limits.h>
char *ft_itoa(int n) {
  char s[50];
  // char s[1024];
  int i;
  int neg;

  i = 0;
  neg = 0;
  //if (n == 0)          // special case not need with do loop
  //    s[i++] = '0';

  if (n < 0) {           // Insure n is _not_ positive
    // n = n * (-1);
    neg = 1;
  } else {
    n = -n;              // no overflow possible here
  }
  // while (n != 0)
  do {
    // s[i++] = (n % 10) + 48;
    s[i++] = '0' - (n % 10);   // subtract
    n /= 10;
  } while (n);
  if (neg) s[i++] = '-';
  s[i] = '\0';
  return strdup(strrev(s));
}

对于清理后的版本

#include <limits.h>

// Compute max size need to represent an `int`
#define INT_DEC_SIZE (sizeof (int)*CHAR_BIT/3 + 3)

char *ft_itoa(int n) {
  char s[INT_DEC_SIZE];
  char *p = &s[sizeof s - 1];
  *p = '\0';
  int i = n;
  if (i > 0) {
    i = -i;
  }
  do {
    p--;
    *p = '0' - (i % 10);
    i /= 10;
  } while (i);
  if (n < 0) *(--p) = '-';
  return strdup(p);
}

INT_DEC_SIZE ref

【讨论】:

  • 使用static持久内存是不正当的! OP明确表示结果已分配,为什么要删除此功能并引入潜在的难以发现的错误?
  • @chqrlie 没有注意到 OP 的 strrev()malloc()。修改了代码。
  • 您应该在清理后的版本中包含&lt;limits.h&gt; for CHAR_BIT
  • @chqrlie 当包含stdlib.h 时,我的编译拾取CHAR_BIT。同意&lt;limits.h&gt; 是更好的头文件。
  • 虽然strdup不是标准函数,但通常在&lt;stdlib.h&gt;中声明,所以两者都需要。
猜你喜欢
  • 2021-05-23
  • 1970-01-01
  • 2021-10-25
  • 2012-04-22
  • 1970-01-01
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多