给定以下示例,该示例分析仅包含有效数字字符的字符串,但对于类型long int
[0] 来说很大。
char *endptr = NULL;
long int val = strtol("0xfffffffffffffffffffffffff", &endptr, 0);
据我了解,捕获此错误的唯一方法是检查是否设置了errno
[1]。 尽管errno
应该是线程安全的[2],但在大多数实时嵌入式系统[3]中,情况并非如此,其中errno
是全局int
- 这是正确的吗?在这种情况下,errno
不能被信任,因为它可能是从中断[4]上下文中修改的。
有没有办法在不使用errno
的情况下捕获此错误,或者有其他解决方法?实现自定义strto*
似乎不是理智的解决方案,但也许没有其他方法?
[0] 类似的例子可以构造strto*
族中的其他函数,如strtod
、strtof
、strtold
、strtoll
、strtoul
和strtoull
。
[1] http://man7.org/linux/man-pages/man3/strtol.3.html
[2] errno 线程安全吗?(也是相关问题(
[3] 在"裸机"项目或低级RTOS(如Apache mynewt,Zephyr或FreeRTOS(中的示例。
[4] 根据中断或操作系统调度程序可能提供的其他上下文进行修改。我相信这些通常只管理堆栈,仅此而已。
如何在没有错误的情况下检查
strto...()
溢出?
首先,我怀疑OP的断言:"在大多数实时嵌入式系统中,errno是全局整数的大多数实时嵌入式系统[2]并非如此 - 这是正确的吗?">尽管如此,这里有一些想法。
- 对于浮点 (FP(,重写
strto...()
容易失败。如果您的 FP 支持无穷大,只需使用isinf()
来检测溢出 - 即使输入字符串是"无限" - 或测试此类无穷大字符串。
接近 0.0 的小值溢出是strto...()
问题的兔子洞。 我假设 OP 不是在那里寻找溢出。
-
对于整数
strto...()
,重写并不难。 很容易找到好的源代码。 如果仍然想避免签名strto...()
,请调用strtol()
和strtoul()
并比较结果。 函数中的溢出返回不比较的结果。 也适用于像"-123"
这样的long
范围内的负片。 对于未签名的strto...()
- 必须考虑这一点。bool strtol_no_errno(const char *s, long *num( { *num = strtol(s, NULL, 0(; 无符号长 unum = strtoul(s, NULL, 0(; 返回 unum == (无符号长( *数字; }
-
对于整数
strto(u)l()
,当long
小于long long
时,只需调用strto(u)ll()
并测试结果是否在范围内。
测试
#include <stdbool.h>
#include <stdio.h>
int main( ) {
char buf[100] = "-1";
for (int i = 0; i<21; i++) {
long num;
bool ok = strtol_no_errno(buf + 1, &num);
printf("%30s %d %ldn", buf + 1, ok, num);
ok = strtol_no_errno(buf + 0, &num);
printf("%30s %d %ldn", buf, ok, num);
strcat(buf, "0");
}
}
输出
1 1 1
-1 1 -1
10 1 10
-10 1 -10
...
100000000000000000 1 100000000000000000
-100000000000000000 1 -100000000000000000
1000000000000000000 1 1000000000000000000
-1000000000000000000 1 -1000000000000000000
10000000000000000000 0 9223372036854775807
-10000000000000000000 0 -9223372036854775808
100000000000000000000 0 9223372036854775807
-100000000000000000000 0 -9223372036854775808
您应该做的第一件事是清理输入。例如,如果没有"0x"的字符串长度大于sizeof(long)*2
,则输入显然太大。因此,首先要验证这一点。
还要确保如果输入的字符串长度正好为sizeof(long)*2
,则对于有符号long
,input[0]
必须'7'
或更小。或者,如果input[0]
'-'
,请检查input[1]
。(这里要考虑一些 2 的补充技巧,我会留给你。
通过上述健全性检查,应该不可能溢出。但如果这样做,该函数保证返回LONG_MAX
(或LONG_MIN
为下溢(。此外,endptr
设置为输入字符串的开头,以防出现此类错误。因此,您可以像这样检查转换是否失败:
if(val != LONG_MAX)
{
/* OK, normal case */
}
else // val == LONG_MAX
{
if(endptr != input &&
(size_t)(endptr-input) == sizeof(long)*2))
{
/* OK, input was LONG_MAX but no overflow */
}
else
{
/* overflow error */
}
}
假设input
没有0x
前缀 - 如果有,则必须相应地调整代码。
下溢需要相同的检查。