2016-03-18 82 views
6

我一直在试图正确地将一个字符数组转换为长整数strtol,检查是否有上溢或下溢,然后对长整型进行转换。一路上,我发现有大量的代码看起来像这样为什么你不能检查errno是否等于ERANGE?

if ((result == LONG_MAX || result == LONG_MIN) && errno == ERANGE) 
{ 
    // Handle the error 
} 

为什么你不能只说

if(errno == ERANGE) 
{ 
    // Handle the error 
} 

从我的理解,如果发生溢或溢出,errno设置以两种情况下的ERANGE。前者是否真的有必要?单独检查ERANGE可能会产生问题吗?

这是我的代码看起来是截至目前

char *endPtr; 
errno = 0; 
long result = strtol(str, &endPtr, 10); 

if(errno == ERANGE) 
{ 
    // Handle Error 
} 
else if(result > INT_MAX || result < INT_MIN) 
{ 
    // Handle Error 
} 
else if(endPtr == str || *endPtr != '\0') 
{ 
    // Handle Error 
} 

num = (int)result; 
return num; 

如果是前者,请让我知道一个道理。

+0

通过您给我们的有限背景很难说清楚。 –

+0

我从来没有见过一个很好的解释,为什么有必要检查'LONG_MAX/LONG_MIN'和'ERANGE'。除了手册页显示这一事实。我能想到的唯一明智的用例是区分溢出和下溢。我也有兴趣知道是否有其他原因。 – kaylum

+0

@kaylum我不知道我觉得我的例子是正确的,主要是因为我不打算区分是否发生溢出或下溢,并且在两种情况下errno都设置为ERANGE。如果其中任何一个发生,比结果无效。 –

回答

5

第一个代码片段只是错误的,我会解释为什么以后,但首先我们需要一些背景。

errno是一个thread-local变量。当系统调用或某些库函数失败时,它被设置为非零值。系统调用成功时,它保持不变。所以它总是包含失败的上次调用的错误号。

这意味着你有两种选择。在每次调用之前将errno设置为0,或者使用errno的标准惯用语。下面是标准的成语

if (foo() == some_value_that_indicates_that_an_error_occurred) 
    then the value in errno applies to foo 
else 
    foo succeeded and the errno must be ignored because it could be anything 

大多数程序员将使用标准的成语伪代码,因为每个系统调用之前设置errno为0是烦人的,重复的,重复的,烦人,恼人的重复。更何况你可能会忘记在实际上很重要的地方将errno设置为0。


回到第一个代码片段。这是错误的,因为strtol没有明确表示strtol失败的返回值。如果strtol返回LONG_MAX,则可能是发生了错误,或者该字符串实际上包含数字LONG_MAX。没有办法知道strtol调用是成功还是失败。这意味着标准成语(这是第一个代码片段试图实现的内容)不能与strtol一起使用。

要正确使用strtol,则需在通话之前设置errno为0,这样

errno = 0; 
result = strtol(buffer, &endptr, 10); 
if (errno == ERANGE) 
{ 
    // handle the error 
    // ERANGE is the only error mentioned in the C specification 
} 
else if (endptr == buffer) 
{ 
    // handle the error 
    // the conversion failed, i.e. the input string was empty, 
    // or only contained whitespace, or the first non-whitespace 
    // character was not valid 
} 

注意一些实现定义errno其他非零值。有关详细信息,请参阅适用手册页。

+1

好的答案,但有一点:严格来说,'strtol'是一个库函数,而不是系统调用。而且它操作'errno'的方式很不寻常。大多数(所有?)系统调用在失败时设置“errno”,但很少有库函数可以。 –

+0

感谢您的回应。如果你不想问我,但是有可能'errno!= 0 && result == 0'?我已经看到了代码,在那里他们合并,并且如果((结果== LONG_MAX ||结果== LONG_MIN)&& errno == ERANGE)'就像你在调用Strol转换A123之前将errno设置为0一样。您希望结果为0,并且errno保持为零。在那种情况下可能会导致errno不为零? –

+0

@SteveSummit确实如此,但我不确定如何在不引入不必要的复杂性的情况下将其纳入答案。我希望可以留下评论。 – user3386109

2

如果你打电话

result = strtol("-2147483648", NULL, 0); 

result = strtol("2147483647", NULL, 0); 
在32位机器上

,你会得到LONG_MINLONG_MAXresult,尽管还没有一个错误。

正如user3386109解释的那样,从strtol检测错误的一种方法是首先将errno设置为0。另一种方法是让它给你一个结束指针并看看它。有三种或四种情况:

char *endptr; 
long int result = strtol(str, &endptr, 10); 
if(*str == '\0') { 
    /* str was empty */ 
} else if(endptr == str) { 
    /* str was completely invalid */ 
} else if(*endptr != '\0') { 
    /* numeric result followed by trailing nonnumeric character(s) */ 
} else { 
    /* str was a completely valid number (perhaps with leading whitespace) */ 
} 

根据您的需要,前两种或三种情况可能会一起折叠。您可能需要担心(a)“完全有效的数字”是否可表示(您可以使用errno进行测试),以及(b)是否有任何“尾随的非数字字符”是无害的空格(唉,strtol不检查你,所以如果你在意你必须检查你自己)。

+0

所以说if(result> INT_MAX || result

+0

@LuisAverhoff对不起,我说'INT_MAX'的意思是'LONG_MAX'。 (现在修正了。)但总的来说,我会说没有任何理由明确测试'strtol'的返回值与任何最小或最大值。 (如果要将值存储到纯int中,唯一的原因可能是与“INT_MIN”和“INT_MAX”进行比较。) –

+0

是的,我正在将该值存储到纯int中。 –

相关问题