无法让 strtol() 检测溢出

Can't get strtol() to detect overflow

此程序使用词法扫描器将标记分类为符号、字符串、十进制数、十六进制数……当检测到 "number" 时,将其交给 strtol() 将其转换为内部 32 位二进制值。但是我无法让 strtol() 可靠地 return 溢出错误。

部分转换代码为:

    errno = 0;      // erase any previous error in errno
    switch (constType) {
…
case lxHex:         // hexadecimal number X'1234567890ABCDEF' (X-string)
fprintf(stderr,"** FindConstantFromString - converting %s\n",constBuffer);
        newDictEntry->dcValue = strtol(constBuffer+2, NULL, 16);
int myerr = errno;
fprintf(stderr,"     value %x errno %d\n",newDictEntry->dcValue, myerr);
        newDictEntry->dcType = syNumber;
        newDictEntry->dcSubType = 4;    // hexadecimal
        if  (   EINVAL == errno
            ||  ERANGE == errno
            ) {
            ErrDict = newDictEntry;
            AnaError (ConstMsg+2);
            newDictEntry->dcType = sySLit;
        };
        result.cstClass = newDictEntry->dcType;
        return result;
…

当使用错误输入测试此代码时,它仅在第一个十六进制数字 >= 8(可能给出负值)时检测到溢出,如以下所示:

    29            | declare v;
    30            | v = x'fedcba9876543210'
** FindConstantFromString - meeting x'fedcba9876543210' as 11
** FindConstantFromString - converting x'fedcba9876543210'
     value ffffffff errno 34
*Error 32: Candidate number x'fedcba9876543210' too large or could not be converted
*Error 20: Unrecognisable lexical unit x'fedcba9876543210' at 30.5
    31            |     + x'123456789abcdef'
** FindConstantFromString - meeting x'123456789abcdef' as 11
** FindConstantFromString - converting x'123456789abcdef'
     value 89abcdef errno 0
    32            |     + 9876543210
** FindConstantFromString - meeting x'fedcba9876543210' as 8
                            symbol already known
** FindConstantFromString - converting x'fedcba9876543210'
     value 0 errno 0
*Error 32: Candidate number x'fedcba9876543210' too large or could not be converted
** FindConstantFromString - meeting 9876543210 as 8
** FindConstantFromString - converting 9876543210
     value 4cb016ea errno 0
    33            |     + '12345a'
** FindConstantFromString - meeting 12345a as 3
    34            |     + '';
** FindConstantFromString - meeting 12345a as 8
                            symbol already known
** FindConstantFromString - converting 12345a
     value 3039 errno 0
*Error 32: Candidate number 12345a too large or could not be converted
** FindConstantFromString - meeting  as 3
** FindConstantFromString - meeting  as 8
                            symbol already known
*Error 33: Empty string cannot be converted to number

在第 30 行,词法扫描器识别出一个十六进制数并请求从该十六进制形式进行转换 (11 = lxHex)。 strtol()errno 正确设置为 ERANGE 并发出错误消息。然后将溢出的十六进制数作为字符串保存在字典中。

请注意 returned 值为 -1,而不是 LONG_MAX

在第 31 行,我们再次遇到另一个溢出的十六进制数,但它不是以 8-9a-f 开头。它再次被检测为十六进制数。尝试进行转换,但根本未设置错误号。该值对应于数字的低 32 位。由于这被认为是成功的,因此保留截断值作为结果。

+应用于"x'fed…'"和89abcdef时,尝试对字符串进行另一次转换"x'fed…'"应该是十进制数(由 8 请求表示)并且转换失败,因为 "x" 不能以十进制数开头。

在第 32 行,我们有一个溢出的十进制数 987654321。再一次,未检测到溢出(代码未显示但类似于十六进制数字的代码,但在 "endptr" 上添加了一个测试,因为字符串可能不会被词法扫描器过滤并包含非法字符)。 returned 值包含数字的最少 32 位。

如果我将 strtol() 更改为 strtoul(),第一个 ERANGE 错误就会消失,我得到了数字的最少 32 位。

我做错了什么?

系统:Fedora Linux 29 glibc: 2.27

strtol() 从给定的字符串中导出 long(即 signed long)值。它只关心它内部构造的 64 位 long 值的上溢或下溢,如果没有遇到问题,它最终会 return 给它的调用者。它根本不关心 32 位值的上溢或下溢。

这就是为什么 strtol() 仅 return 在您显示的示例中出现错误,其中 long 值会溢出为负 64 位数字。 (正如您所指出的,strtoul() 在这种情况下不会抱怨,因为在这种情况下 unsigned long 值没有溢出。您需要将 strtoul() 一个 17 位的字符串提供给溢出 unsigned long.)

strtol() 也不知道或不关心您的程序将采用其 64 位 long 结果并通过将值分配给 32 位变量立即丢弃其高 4 字节。这种截断就是为什么您会认为“returned 值是 -1,而不是 LONG_MAX”。事实上 strtol() 的结果是 LONG_MAX,但是你的程序已经丢弃了 LONG_MAX 的前 4 个字节,只剩下低 4 个字节,当作为 32 位 int.

处理时,其值为 0xffffffff-1

如果您想使用 strtol() 来生成和检查 32 位值,那么您必须自己进行额外的范围检查。首先将strtol()的结果收集到long变量中,并在strtol()的执行过程中检查该结果是否表示64位溢出或下溢,然后可以比较long 针对 INT_MAXINT_MIN 的结果,以查看其值是否会溢出或下溢 32 位变量。

显然,您可以将其封装在一个小函数中,该函数(如果您对 errno 进行适当的修改)的行为就像 strtol() 一样,只是它适用于 int值而不是 long。但是,您应该克制将函数名称命名为 strtoi 的冲动,因为以 str[a-z] 开头的名称已被 POSIX 保留以供将来在标准库中使用。一些系统可能已经有一个 strtoi 并且 Linux 可能有一天会得到一个。