C语言中的整数解析
在标准的libc API集中,提供了多个将ASCII数字转换为整数的函数。它们虽然方便易用,但也容易出错,并且在接受输入时过于宽松,常常会默默地“吞下”不规范的输入。
atoi
atoi() 可能是最常见和基础的函数。它将字符串转换为有符号整数。还有它的伙伴函数atol(),它转换成长整型。
这些函数存在的问题包括:它们在出错时返回0,而没有错误指示;不检查下溢或溢出;并且在atol()的情况下,long类型在不同平台上的大小是不同的挑战。因此,它们都不能可靠地用于64位数字。它们也无法告知数字在哪里结束。
使用这些函数会使你的解析器无法检测和处理错误或奇怪的输入。当我们避免使用这些函数时,可以编写更好、更严格的解析器。
strtol
这个函数,连同它的“兄弟姐妹”strtoul()、strtoll()等,功能更强大。它们具有溢出检测功能,并且可以检测错误——例如,如果根本没有数字可解析。
然而,这些函数也同样过于乐意地接受前导空白,并允许在数字前出现正负号(+或-)。这些函数的long版本存在long并非在所有平台上都是64位的问题,而long long版本的问题在于它并非在所有平台都可用。
使用这些函数进行溢出和下溢检测相当怪异,涉及到errno,并且迫使我们每次调用时都要多写几行条件语句,以确保能捕获这些情况。
cURL 的代码实践
我认为,cURL项目以及整个世界多年来已经认识到,在解析协议和数据时,严格通常比宽松更好,而不是试图接受很多东西并猜测它可能意味着什么。
这一认识的直接结果是,我们确保cURL按照数据应有的样子来解析和解释数据,并且一旦检测到数据错误就立即报错。出于安全性和功能的稳健性考虑,语法不正确的数据是不被接受的。
这也意味着所有的数字解析都必须是精确的,正确处理溢出和允许的最大值,并且必须检测错误。它始终支持高达64位的数字。
strparse
我之前曾写过博客介绍我们如何在cURL中实现自己的一套解析函数,其中也包括数字解析。
我们创建的函数中最常用的是curlx_str_number()。它解析一个字符串并将值存储在一个64位变量中(在cURL代码中,这种变量始终存在且始终是64位)。它还有一个最大值参数,如果值太大则返回错误。当然,它也会在溢出等情况时报错。
我们的这个函数不允许任何前导空白,当然也不允许前缀的加号或减号。如果需要允许这些,周围的解析代码需要明确地允许它们。
curlx_str_number函数可能比它替代的函数稍慢一些,但我认为差异不大,而它带来的便利性和增加的严格性是非常受欢迎的。通过这种方式,我们编写了更好的代码和解析器,也更具安全性。(curlx_str_number源代码)
发展历程
截至昨天,即2025年11月12日,所有这些较弱的函数调用都已从cURL源代码中清除。图中2025年初的那次下降是我们清除所有strtol()变体的时候。昨天,我们最终清除了最后的atoi()调用。
(该图表每日更新)
curlx
上述函数使用了'curlx'前缀。我们在cURL代码中使用这个前缀来表示那些存在于libcurl源代码中,但也可被curl工具使用的函数——共享相同的代码,而它们并不通过libcurl API提供。
这是我们为了减少代码重复并在库和命令行工具之间共享代码而采取的一种做法。