【问题标题】:itoa() c implementation int min underflowitoa() c 实现 int min 下溢
【发布时间】:2016-10-08 07:42:37
【问题描述】:

我正在针对我的 itoa() 函数运行一些测试用例,但不断得到 ​​p>

did not allocate memory for the int min value

我正在检查,但我在这里遗漏了一些东西,它是什么?

char *ft_itoa(int x) {
    char *s;
    size_t len;
    long int n;

    n = x;
    if (x == -2147483648)
        return (ft_strdup("-2147483648"));

    len = ft_intlen(n) + 1;
    if (!(s = (char*)malloc(sizeof(char) * len)))
        return (NULL);

    if (n == 0)
        s[0] = '0';

    if (n < 0) {
        s[0] = '-';
        n = -n;
    }
    s[len - 1] = '\0';
    while (n) {
        len--;
        s[len - 1] = (n % 10) + '0';
        n /= 10;
    }
    return (s);
}

【问题讨论】:

  • 您需要发布 ft_intlen 函数的完整代码
  • 就是完整的代码,strdup只是分配字符串,ft_intlen只是返回字符串的长度,都通过测试用例
  • 你的系统中 int 的大小是多少?
  • 您还想决定是使用 C 还是 C++。标题声明 C,转换 malloc() 表示 C++,问题标记为 ...
  • ft_intlen 很重要,你应该展示它,但如果你不想展示它,那就这样。您还需要显示失败的测试用例。

标签: c int overflow itoa


【解决方案1】:

这一行:

if (x == -2147483648)

不做你认为它做的事。 C 没有负整数常量。这是一个值为 2^31 的无符号整数常量,您可以在其上应用一元减号运算符。这意味着表达式 x == -21... 将取决于您的编译器使用的 C 标准。

如果你使用 C99 或 C11,你会没事的。有一个足够大的有符号类型——long long 保证对于这个数字足够大,所以 x 和 -21... 都会被转换成 long long 然后进行比较。但是,如果您使用的是 C89 编译器并且您的机器没有足够长的类型,那么您将在此处遇到实现定义的行为:

当一个整数被降级为一个更小的有符号整数,或者一个无符号整数被转换为其对应的有符号整数时,如果该值不能被表示,则结果是实现定义的。

这就是人们说要使用limits.h 的原因。不是因为他们很迂腐,而是因为这是一个危险的领域。如果您仔细查看limits.h 包含的内容,您很可能会发现这样一行:

#define INT_MIN (- INT_MAX - 1)

这个表达式实际上有正确的类型和值。

除此之外,我在您发布的代码中看不到任何错误。如果这不是问题,则ft_intlenft_strdup 都是错误的。或者你在测试中调用你的函数是错误的(同样的问题也适用于 -21... 调用测试时)。

【讨论】:

  • if (x == -2147483648) 很好,除了 INT_MIN 可能有不同的值。使用 C99/C11 2147483648-2147483648 可以很好地适应 long long。对于C89(和32位长),2147483648unsigned long“整数常量的类型是可以表示其值的相应列表的第一个。无后缀十进制:int,long int,unsigned long int ;" C89 3.1.3.2 整数常量。引用不适用,因为 -== 是用 unsigned long 数学完成的。 “如果任一操作数的类型为 unsigned long int,则另一个操作数将转换为 unsigned long int。” 3.2.1.5
  • @chux 通常的算术转换实际上并没有明确说明一元运算符应该如何表现,但我在 gcc 中验证了(有趣的是 _Generic 在 c86 模式下的 gcc 中工作)转换是有符号的诠释。即使相等是用 unsigned long 完成的,它也只是意味着我们正在将一个无法表示的值转换为 unsigned long,这又是实现定义的。
  • int 转换为任何无符号类型是明确定义的,而不是实现定义的。 “否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。” C11 6.3 转换 所以 if (x == -2147483648) 在 32 位长的 C89 机器上变为 if (((unsigned long)x) == 2147483648UL)
【解决方案2】:

状态:已解决无效

原因:WORKS_FOR_ME

无论如何,我在某些方面有所改进。

  • sizeof(char) 始终为 1,不需要它。
  • 不要投malloc
  • 如果您处理特殊情况 0,则只需一次性处理。
  • -2147483648 非常非常糟糕。这就是 INT_MIN 的用途。
  • return 不是函数,不返回(value),只返回value
  • 不要一直s[len - 1],最好在进入循环之前减少len。或者,由于您仅在 malloc 调用中需要 len + 1,因此只需将 len 作为 intlen 返回它并使用 len + 1 调用 malloc

ft_itoa.c

#include <stdbool.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <btstr.h>

int ft_intlen(int n) {
        char buffer[8192];
        return snprintf(buffer, sizeof buffer, "%i", n);
}

char * ft_itoa(int n) {
        char * s;
        size_t l, len;
        bool fix_int_min = false;

        if (!n) {
                return mstrcpy("0");
        }

        if (-INT_MAX != INT_MIN && n == INT_MIN) {
                ++n;
                fix_int_min = true;
        }

        len = ft_intlen(n);
        if (!(s = malloc(len + 1))) {
                return NULL;
        }
        if (n < 0) {
                s[0] = '-';
                n = -n;
        }
        s[l = len] = '\0';
        while (n) {
                s[--len] = (n % 10) + '0';
                n /= 10;
        }

        if (fix_int_min) {
                --l;
                while (s[l] == '9') {
                        s[l++] = 0;
                }
                if (s[l] == '-') {
                        // realloc +1 and write "-1[0....0]\0"
                } else {
                        ++s[l];
                }
        }

        return s;
}

ma​​in.c

#include <limits.h>
#include <stdio.h>

char * ft_itoa(int n);

void check(int n) {
        printf("%i = %s\n", n, ft_itoa(n));
}

int main() {
        check(0);
        check(-1);
        check(1);
        check(23);
        check(42);
        check(4711);
        check(1000);
        check(INT_MAX);
        check(1+INT_MIN);
        check(INT_MIN);
}

结果

$ gcc -W -Wall -Wextra -lBtLinuxLibrary ft_itoa.c main.c -o ft_itoa && ./ft_itoa
0 = 0
-1 = -1
1 = 1
23 = 23
42 = 42
4711 = 4711
1000 = 1000
2147483647 = 2147483647
-2147483647 = -2147483647
-2147483648 = -2147483648

【讨论】:

  • 我试图在没有限制的情况下使用它,并且在某些地方有括号是惯例。这是完全可以接受的。
  • 在 (c) 中你 ((can) 写 (as)) 许多 (括号 (as (you (want)))))。真的。问题是:这有用吗?关于limits.h的事情:它的存在是有原因的。尝试不这样做并不是一个可以接受的答案。因为,如果你想不这样做,为什么不只是 malloc(12) 而是 ft_intlen() 呢? (请不要告诉,因为你只需要 8 个字节,今天,每个 malloc 返回 n*16 个字节,其中 n 是一个整数。所以,无论如何你得到 16 个字节。
  • 我只是说括号的东西甚至不应该是一个论点,它无关紧要
  • 对我来说,这是一个进步。如果你不同意,那很好,忽略这一点并继续在你的 return 语句周围写括号。这不是错误。
  • 这只是我必须,测试检查约定太大声笑所以我已经习惯了,但不一定喜欢它。我同意你的看法。
【解决方案3】:

你不需要那张支票。而是将其转换为unsigned,这将适合绝对值:

size_t ft_uintlen(unsigned n)
{
    size_t len = 0;
    do {
        ++len;
        n /= 10;
    } while(n);
    return len;
}

char *ft_itoa(int x)
{
    char    *s;
    size_t  len;
    unsigned n;
    int negative;

    negative = x < 0;
    n = negative ? 0-(unsigned)x : (unsigned)x;
    len = ft_uintlen(n) + negative + 1;
    if (!(s = (char*)malloc(len)))
        return (NULL);

    s[--len] = '\0';
    if (negative)
        s[0] = '-';
    do {
        s[--len] = (n % 10) + '0';
        n /= 10;
    } while(n);
    return (s);
}

请注意,这使用了一个新的 size_t ft_uintlen(unsigned) 函数,该函数适用于 unsigned 参数。

【讨论】:

  • 如果x == INT_MIN 因为算术溢出,恐怕n = negative ? -x : x; 会调用未定义的行为。
  • @chqrlie:抱歉,忘记转换为未签名。固定。
  • unsigned 通常有大约 2 倍的范围为 int。根据 C 规范 INT_MAX == UINT_MAX 是可能的。在这种罕见的情况下,转换为unsigned 并不能与INT_MIN 形成正确的结果。
  • 简化:使用此代码if (n == 0) s[0] = '0'; else 不需要。
  • @chux:谢谢!我喜欢这样的简化!关于INT_MAX == UINT_MAX:我想可以,但只要今天没有任何这样的架构在使用I consider it a theoretical spec m10n
【解决方案4】:

您的溢出防止机制可能存在问题。您尝试将int 类型的x 分配给n,类型为long int。但是规范并不能保证long int 类型可以处理大于int 的值范围。更多信息可以找到"Long Vs. Int"

如果您的编译器支持,请使用long long int 类型作为n。将您的 ft_intlen 函数更新为 int ft_intlen(long long int n)。在这种情况下,您将能够处理整个 int 类型值范围并删除以下行:

if (x == -2147483648)
  return (ft_strdup("-2147483648"));  

还有错误消息did not allocate memory for the int min value 不是system error numbers 之一。您需要在应用程序中添加更多日志记录,尤其是在由于某种原因无法调试它的情况下。为每个系统函数调用检查errno,例如:

char* errmsg;
// Other code skipped here 
if (!(s = (char*)malloc(sizeof(char) * len)))
{
  errmsg = strerror(errno);          // Use strerror_s if possible 
  printf("Malloc error: %s\n", errmsg);
  return (NULL);
}

【讨论】:

  • 注意:规范不保证 long long int 类型可以处理大于 int 的值范围。
【解决方案5】:

潜在的代码故障,按怀疑顺序排列:

  1. ft_strdup() 因为该代码使用“int min value”调用并发生错误。
  2. 缺少各种功能的原型。尤其是ft_strdup()/strdup()
  3. 调用/测试代码有问题。
  4. “int min value”大于-2147483648。 (最好使用INT_MIN。)
  5. ft_intlen(n) 编码错误并返回INT_MAX,然后代码尝试malloc(INT_MIN)
  6. int/long 均为 64 位。这会将第一个 s[len - 1] = (n % 10) + '0';INT_MIN 混淆。

否则,如果INT_MIN 的值为-2147483648,则ft_itoa(int x) 可以。


OP 断言“... strdup 只是分配字符串,ft_intlen 只是返回字符串的长度,两者都通过了测试用例 – franklinexpress 10 月 8 日 7:52”

通过测试用例并不意味着它可以在不调用未定义行为的情况下工作。最好发布ft_intlen()ft_strdup() 和测试工具以供审查。


候选便携实现。不依赖于 int/long 大小或 2 的补码。除了CHAR_BIT 之外,不需要&lt;limits.h&gt;,代码可以假设为8,而不会牺牲太多的可饮用性。适用于 C89/99/11。

// Buffer size needed to decimal print any `int`
// '-' + Ceiling(value bit size * log10(2)) + \0
#define INT_STR_SIZE (1 + ((CHAR_BIT*sizeof(int) - 1)/3 + 1) + 1)

char *ft_itoa(int x) {
  char buf[INT_STR_SIZE];
  char *s = buf + sizeof buf - 1;  // Set to end of buffer
  *s = '\0';

  int n = x; // no need for wider types like long

  if (n > 0) {
    // fold positive numbers to negative ones
    // This avoids the special code for `INT_MIN` and need for wider types
    n = -n;
  }

  // Using a do loop avoids special code for `x==0`
  do {
    // Use `div()` rather than / % in case we are using C89.
    // / %  has implementation defined results for negative arguments.
    div_t qr = div(n, 10);
    *--s = (char) ('0' - qr.rem);  // Form digit from negative .rem
    n = qr.quot;
  } while (n);

  if (x < 0) {
    *--s = '-';
  }

  // Double check ft_strdup() is coded correctly
  // Insure calling code frees the buffer when done.
  return ft_strdup(s); 
}

【讨论】:

    【解决方案6】:

    您提供的代码在 OsX 上编译并运行,但使用我自己的 ft_stdupft_intlen。因此,您可以向我们展示代码或检查它们是否存在错误。 我做了一些测试(包括2147483647,-2147483648)。效果很好。

    不管怎样,这些行:

    if (x == -2147483648) return (ft_strdup("-2147483648"));

    只要在进行任何类型的操作之前将x 值复制到long long 变量(Art)中就没有用了。所以你不需要包括types.h(臭名昭著的moulinette不会给你-42)。

    碰巧在 OsX 上它也适用于 long 值,但这是不可移植的安全。

    【讨论】:

      【解决方案7】:

      只需使用:

      INT_MIN
      

      代替:

      -2147483648
      

      在你的测试中:

      if (x == INT_MIN)
          return (ft_strdup("-2147483648"));
      

      原因是某些编译器可能无法理解该数字。

      标准C库limits.h通常定义为:

      #define INT_MIN  (-INT_MAX - 1)
      

      为了避免这个问题。

      【讨论】:

        猜你喜欢
        • 2013-05-08
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 2012-04-22
        • 1970-01-01
        • 2016-05-23
        • 2012-02-04
        • 2014-12-28
        相关资源
        最近更新 更多