开发者生态
morning
无法解析 C 中的整数 (2022)
2026-05-20
1 阅读
konmok
C 标准库中有几种方法可以尝试将字符串解析为数字。他们都坏了。底部更新:实际上 C++ 的 std::from_chars() 看起来很有用。抛开宽字符版本不谈,继续使用 long (跳过 int 、 long long 或 intmax_t ,这些变体都有同样的问题),我能想到三种方法: atol() strtol() / strtoul() sscanf() 它们都被破坏了。无论如何,什么是正确的行为?首先我要声明一个常识:“当我看到它时我就知道它”。我用眼球在字符串中看到的数字一定是以适当的数据类型存储的数值。 “123”必须变成数字123。另一个标准是必须解析整个数字。一出现问题就停下来,然后返回任何可能是正确的东西,这是不对的。 “123timmy”不是数字,也不是空字符串。未能提供上述内容必定是一个错误。或者至少作为解析器的用户,我必须可以选择知道它是否发生。首先: atol() 输入输出 123timmy 123 99999999999999999999999999999999 LONG_MAX timmy 0 空字符串 0 " " 0 否。全部错误。并且呼叫者无法知道发生了什么。对于 LONG_MAX 溢出情况,联机帮助页不清楚它是否应该这样做或返回尽可能多的 9,但根据经验,在 Linux 上这就是它的作用。 POSIX 和 C 都说“如果值无法表示,则行为未定义”。我勒个去?这使得 atol() 无法用于不受信任的输入。现在,在实践中,我没有看到编译器和 libc 在错误输入时触发 UB 的可怕部分,但在纸面上 atol() 被允许在输入错误时擦除你的硬盘。伟大的。如果无法检查错误,我该如何知道该值是否可以表示?因此,如果您将字符串传递给 atol(),那么您基本上会得到一个随机值,并且大多数时候都倾向于正确。我可以原谅 atol() 。这是从一个更简单的时代开始的,那时 gets() 似乎是个好主意。众所周知,gets() 不能正确使用。 atol() 也不能。下一篇:strtol() 我现在要反驳这篇文章的标题。 strtol()实际上可以正确使用。 strtoul() 不能,但如果您只对有符号类型感到满意,那么这实际上可以工作。但只是小心翼翼。手册页有示例代码,但以函数形式表示: bool parse_long ( const char * in , long * out ) { // 检测空字符串。 if ( !* in ) { fprintf ( stderr , "空字符串 \n " );返回假; } // 解析数字。 char * endp = NULL ; // 这将指向字符串的末尾。错误号=0; // 将 errno 预设为 0。 * out = strtol ( in , & endp , 0 ); // 范围错误以 errno 形式传递。 // IE。在 amd64 Linux 上,它需要介于 -2^63 和 2^63-1 之间。 if ( errno ) { fprintf ( stderr , "解析错误: %s \n " , strerror ( errno ));返回假; } // 检查字符串末尾是否有垃圾。 if ( * endp ) { fprintf ( stderr , "解析不完整\n " );返回假;返回真;如果在错误情况下可以破坏 *out,那么这里的 API 就是一个问题,但这只是一个小细节。是的,带符号的数字是可以解析的! strtoul()/strtoul() 怎么样?与它的兄弟函数不同,该函数无法正确使用。 strtoul() 函数返回转换结果,或者如果有前导减号,则返回转换结果的负数,表示为无符号值 amd64 Linux 上的示例输出: 输入 raw 输入 Output raw 输出 -1 -1 18446744073709551615 2^64-1 -9223372036854775808 -2^63 9223372036854775808 2^63 -9223372036854775809 -2^63-1 9223372036854775807 2^63-1“”只是空格错误:endp不为空-18446744073709551614 -2^64+2 2 1 -18446744073709551615 -2^64+1 1 1 -18446744073709551616 -2^64 Error ERANGE 呼,终于报错了。这根本没有什么用处。或者我应该说:也许在某些用例中这很有用,但它绝对不是一个返回我要求的数字的函数。 Linux 手册页中的标题是将字符串转换为无符号长整型。它就是这样做的。从技术上讲,它将其转换为无符号长整数。这显然不是正确的,但它确实返回一个无符号长整型。有趣的是,仅包含空格的非空输入可被检测为错误。这显然是正确的做法,但尚不清楚这是否是故意的。因此,请检查您的实现:如果传递了所有 isspace() 字符的输入,这是否被正确检测为错误?如果没有,那么 strtol() 也可能被破坏。也许 sscanf() ?需要的代码少一点,这很好: bool parse_ulong ( const char * in , unsigned long * out ) { char ch ; // 探测跟踪数据。整数长度; if ( 1 != sscanf ( in , "%lu%n%c" , out , & len , & ch )) { fprintf ( stderr , "无法解析 \n " );返回假; } // 这从未触发,因此 sscanf() 似乎不会停止 // 溢出时的解析。因此可以安全地跳过长度检查。 if ( len != ( int ) strlen ( in )) { fprintf ( stderr , "没有匹配