首页 > 解决方案 > `strtoul()` 对于非常“负”的字符串,正确的返回值是多少?

问题描述

这是一个可以提问和回答自己的问题。我研究了这个问题,发现结果很奇怪,并发布了我的发现。


“非常负面”的字符串应该从strtoul():返回什么值1ULONG_MAX或者什么?


strtol()

对于表示数值的字符串,如"123", 的strtol()行为符合预期。字符串(蓝色)[LONG_MIN ... LONG_MAX]转换为其预期long值。上面的字符串(黄色)LONG_MAX转换为LONG_MAX并设置errno==ERANGE. 下面的字符串LONG_MIN转换为LONG_MIN并设置errno==ERANGE.

strtoul()积极的

对于表示数值的字符串,如"123", strtoul()也按预期运行。字符串(红色)[0 ... ULONG_MAX]转换为其预期unsigned long值。上面的字符串(绿色)ULONG_MAX转换为ULONG_MAX并设置errno==ERANGE.

如果主题序列以减号开头,则转换产生的值被否定(在返回类型中)。C17dr § 7.22.1.4 5

strtoul()消极的

对于字符串(红色)[-ULONG_MAX ... -1],转换否定正转换并添加ULONG_MAX + 1(如将负值典型分配给unsigned)并且不设置errno。这有点令人惊讶,但这就是规范定义它的方式。

strtoul()非常消极 - 问题

对于小于的字符串(绿色)-ULONG_MAX,我希望转换像上面描述的较小的负值一样处理:转换否定正转换(ULONG_MAX由于溢出)并添加ULONG_MAX + 1. 预期结果1(或可能0)与errno == ERANGE. 然而,锻炼strtoul()导致ULONG_MAX

什么是正确的?

斯特图尔

测试代码

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

long strtol_test(const char *s, int base) {
  printf("\n");
  int width = snprintf(NULL, 0, "%ld", LONG_MIN);
  printf("base:%2d \"%s\"\n", base, s);

  char *endptr_signed;
  errno = 0;
  long val_signed = strtol(s, &endptr_signed, base);
  int errno_signed = errno;

  char *endptr_unsigned;
  errno = 0;
  unsigned long val_unsigned = strtoul(s, &endptr_unsigned, base);
  int errno_unsigned = errno;

  if (val_signed < 0 || (unsigned long) val_signed != val_unsigned
      || endptr_signed != endptr_unsigned || errno_signed != errno_unsigned) {
    printf("  signed  val:%*ld end:%2td e:%s\n", width, val_signed,
        endptr_signed - s, strerror(errno_signed));
    printf("unsigned  val:%*lu end:%2td e:%s\n", width, val_unsigned,
        endptr_unsigned - s, strerror(errno_unsigned));
    return 1;
  }

  printf("    both  val:%*ld end:%2td e:%s\n", width, val_signed,
      endptr_signed - s, strerror(errno_signed));
  return 0;
}

int main() {
  char s[][50] = {"-ULONG_MAX1", "-ULONG_MAX", "LONG_MIN1", "LONG_MIN", "-1",
      "-0", "42", "LONG_MAX", "LONG_MAX1", "ULONG_MAX", "ULONG_MAX1", "x"};
  snprintf(s[0], sizeof *s, "-%lu", ULONG_MAX);
  s[0][strlen(s[0]) - 1]++;
  snprintf(s[1], sizeof *s, "-%lu", ULONG_MAX);
  snprintf(s[2], sizeof *s, "%ld", LONG_MIN);
  s[2][strlen(s[2]) - 1]++;
  snprintf(s[3], sizeof *s, "%ld", LONG_MIN);

  snprintf(s[7], sizeof *s, "%ld", LONG_MAX);
  snprintf(s[8], sizeof *s, "%ld", LONG_MAX);
  s[8][strlen(s[8]) - 1]++;
  snprintf(s[9], sizeof *s, "%lu", ULONG_MAX);
  snprintf(s[10], sizeof *s, "%lu", ULONG_MAX);
  s[10][strlen(s[10]) - 1]++;

  strcpy(s[11], s[0]);
  s[11][strlen(s[11]) - 1]++;

  int n = sizeof s / sizeof s[0];
  for (int i = 0; i < n; i++) {
    strtol_test(s[i], 0);
  }
}

样本输出

base: 0 "-18446744073709551616"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:18446744073709551615 end:21 e:Numerical result out of range

base: 0 "-18446744073709551615"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:                   1 end:21 e:No error

base: 0 "-9223372036854775809"
  signed  val:-9223372036854775808 end:20 e:Numerical result out of range
unsigned  val: 9223372036854775807 end:20 e:No error

base: 0 "-9223372036854775808"
  signed  val:-9223372036854775808 end:20 e:No error
unsigned  val: 9223372036854775808 end:20 e:No error

base: 0 "-1"
  signed  val:                  -1 end: 2 e:No error
unsigned  val:18446744073709551615 end: 2 e:No error

base: 0 "-0"
    both  val:                   0 end: 2 e:No error

base: 0 "42"
    both  val:                  42 end: 2 e:No error

base: 0 "9223372036854775807"
    both  val: 9223372036854775807 end:19 e:No error

base: 0 "9223372036854775808"
  signed  val: 9223372036854775807 end:19 e:Numerical result out of range
unsigned  val: 9223372036854775808 end:19 e:No error

base: 0 "18446744073709551615"
  signed  val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned  val:18446744073709551615 end:20 e:No error

base: 0 "18446744073709551616"
  signed  val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned  val:18446744073709551615 end:20 e:Numerical result out of range

base: 0 "-18446744073709551617"
  signed  val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned  val:18446744073709551615 end:21 e:Numerical result out of range

标签: c

解决方案


范围错误结果始终ULONG_MAXstrtoul().

对于像 之类的字符串,这相当简单,"-18446744073709551616"因为正确的(正)值在范围之外,ULONG_MAX因此返回并errno设置为ERANGE。对于范围错误,不存在strtoul()提供任何其他答案的规定。

... 如果正确的值超出了可表示值的范围,则 ... ULONG_MAX, ... 被返回(根据值的返回类型和符号,如果有的话),宏的值ERANGE存储在errno. C17dr § 7.22.1.4 8

因此,当字符串在范围内strtoul()时会产生一个非错误值。[-ULONG_MAX ... +ULONG_MAX]转换后的结果是unsigned long [0 ... ULONG_MAX]. 错误结果总是ULONG_MAX.


推荐阅读