程序使用词法扫描器将令牌分类为符号,字符串,十进制数字,十六进制数字,……当检测到“数字”时,将其移交给strtol()
以将其转换为内部32位二进制数值。但是我无法让strtol()
可靠地在溢出时返回错误。
转换代码的一部分是:
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 = 1xHex)。 strtol()
正确地将errno
设置为ERANGE
并发出错误消息。然后,溢出的十六进制数将作为字符串保存在字典中。
请注意,返回值是-1,而不是LONG_MAX。
在第31行,我们再次有另一个溢出的十六进制数字,但是它不是以8-9a-f开头。再次将其检测为十六进制数字。尝试进行转换,但根本未设置errno。该值对应于数字的低32位。既然成功了,截断的值就会保留下来。
将+
应用于“ x'fed…'”和89abcdef时,将尝试对字符串“ x'fed ...'”进行另一次转换,该转换应为十进制数字(由8个请求表示),并且转换失败,因为“ x”不能以十进制数字开头。
在第32行,我们有一个溢出的十进制987654321。再次,未检测到溢出(未显示代码,但类似于十六进制数字的代码,并在“ endptr”上添加了一个测试,因为可能未过滤字符串)由词法扫描程序提供,并且包含非法字符)。返回的值包含数字的至少32位。
如果我将strtol()
更改为strtoul()
,则第一个ERANGE
错误消失了,我得到了该数字的至少32位。
我在做什么错了?
系统:Fedora Linux 29 glibc:2.27
答案 0 :(得分:1)
strtol()
从给定的字符串中得出一个long
(即signed long
)值。它只关心内部构造的64位long
值的上溢或下溢,并且如果没有遇到问题,它将最终返回到其调用方。它根本不关心32位值的上溢或下溢。
这就是为什么strtol()
仅在您显示的示例中返回错误的原因,其中long
值将溢出为负64位数字。 (正如您所指出的,在这种情况下strtoul()
不会抱怨,因为在这种情况下unsigned long
值不会溢出。您需要将strtoul()
的17位数字字符串unsigned long
溢出。)
strtol()
也不知道或不在乎您的程序将其64位long
的结果并通过将值分配给32位变量立即丢弃其高4位字节。这种截断是导致您认为“ 返回值是-1,而不是LONG_MAX
”的原因。实际上,来自strtol()
的结果是 LONG_MAX
,但是您的程序已经丢弃了LONG_MAX
的前4个字节,而只剩下了低4个字节,视为32位0xffffffff
时,值为-1
或int
。
如果您想使用strtol()
来生成和审查32位值,则必须自己进行其他范围检查。首先将strtol()
的结果收集到long
变量中,并检查该结果是否表示执行strtol()
期间出现64位上溢或下溢,然后可以比较{{1} }对long
和INT_MAX
进行计算,以查看其值是溢出还是下溢32位变量。
显然,您可以将其包装在一个小函数中,该函数(如果您对INT_MIN
进行适当的修补)的行为与errno
相似,只是它适用于strtol()
值比int
。但是,您应该避免给函数起名字long
的冲动,因为以strtoi
开头的名字是POSIX保留的,以备将来在标准库中使用。某些系统可能已经有一个str[a-z]
,而Linux可能有一天会升级。