正确使用strtol

Jim*_*imm 26 c c++

下面的程序将字符串转换为long,但根据我的理解,它也会返回错误.我依赖的事实是,如果strtol将字符串成功转换为long,那么第二个参数strtol应该等于NULL.当我用55运行以下应用程序时,我收到以下消息.

./convertToLong 55
Could not convert 55 to long and leftover string is: 55 as long is 55
Run Code Online (Sandbox Code Playgroud)

如何成功检测strtol的错误?在我的应用程序中,零是有效值.

码:

#include <stdio.h>
#include <stdlib.h>

static long parseLong(const char * str);

int main(int argc, char ** argv)
{
    printf("%s as long is %ld\n", argv[1], parseLong(argv[1]));
    return 0;
 }

static long parseLong(const char * str)
{
    long _val = 0;
    char * temp;

    _val = strtol(str, &temp, 0);

    if(temp != '\0')
            printf("Could not convert %s to long and leftover string is: %s", str, temp);

    return _val;
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*ler 51

请注意,以下划线开头的名称是为实现保留的; 最好避免在代码中使用这些名称.因此,_val应该是公正的val.

strtol()当你第一次遇到它时,错误处理及其亲属的完整规范是复杂的,非常复杂.你正在做的一件事是使用一个函数来调用strtol(); 在代码中使用'raw'可能不正确.

由于问题用C和C++标记,我将引用C2011标准; 您可以自己在C++标准中找到适当的措辞.

ISO/IEC 9899:2011§7.22.1.4的strtol,strtoll,strtoulstrtoull功能

long int strtol(const char * restrict nptr, char ** restrict endptr, int base);

2[...]首先,它们将输入字符串分解为三个部分:一个初始的,可能是空的白色空格字符序列(由isspace函数指定),一个类似于某个基数所表示的整数的主题序列by base的值,以及一个或多个无法识别的字符的最终字符串,包括输入字符串的终止空字符.[...]

7如果主题序列为空或者没有预期的形式,则不进行转换; 如果不是空指针,则值nptr存储在指向的对象中.endptrendptr

返回

8的strtol,strtoll,strtoul,和strtoull函数返回转换后的值,如果有的话.如果无法执行转换,则返回零.如果正确的值超出可表示值的范围,则返回LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,ULONG_MAX或ULLONG_MAX(根据值的返回类型和符号,如果有),并且宏ERANGE的值为存储在errno.

请记住,没有标准C库函数设置errno为0.因此,为了可靠,您必须errno在调用之前将其设置为零strtol().

所以,你的parseLong()功能可能如下所示:

static long parseLong(const char *str)
{
    errno = 0;
    char *temp;
    long val = strtol(str, &temp, 0);

    if (temp == str || *temp != '\0' ||
        ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
        fprintf(stderr, "Could not convert '%s' to long and leftover string is: '%s'\n",
                str, temp);
        // cerr << "Could not convert '" << str << "' to long and leftover string is '"
        //      << temp << "'\n";
    return val;
}
Run Code Online (Sandbox Code Playgroud)

请注意,出错时,返回0或LONG_MIN或LONG_MAX,具体取决于strtol()返回的内容.如果您的调用代码需要知道转换是否成功,则需要一个不同的功能接口 - 见下文.另外,请注意应该打印错误stderr而不是stdout错误消息,错误消息应该由换行符终止\n; 如果他们不是,他们不能保证及时出现.

现在,在库代码中,您可能不希望任何打印,并且您的调用代码可能想要知道转换是否成功,因此您也可能修改该接口.在这种情况下,您可能会修改该函数,以便返回成功/失败指示:

bool parseLong(const char *str, long *val)
{
    char *temp;
    bool rc = true;
    errno = 0;
    *val = strtol(str, &temp, 0);

    if (temp == str || *temp != '\0' ||
        ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE))
        rc = false;

    return rc;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用如下:

if (parseLong(str, &value))
    …conversion successful…
else
    …handle error…
Run Code Online (Sandbox Code Playgroud)

如果你需要区分'尾随垃圾','无效数字字符串','值太大'和'值太小'(和'无错误'),你将使用整数或enum代替布尔返回代码.如果要允许尾随空格而不允许其他字符,或者如果您不想允许任何前导空格,则在该函数中还有更多工作要做.代码允许八进制,十进制和十六进制; 如果你想要严格的十进制,你需要在调用中将0更改为10 strtol().

如果您的函数要伪装成标准库的一部分,则它们不应设置errno0永久性,因此您需要包装代码以保留errno:

int saved = errno;  // At the start, before errno = 0;

…rest of function…

if (errno == 0)     // Before the return
    errno = saved;
Run Code Online (Sandbox Code Playgroud)

  • 标准没有提到为`base`或`2`..`36`之外的`base`设置`errno == EINVAL`,但这是合理的做法.一般来说,你应该谨慎使用`errno`来检测错误条件,而不是从函数返回; 即使函数成功,库也可以将`errno`设置为非零值.(在Solaris上,如果输出不是终端,则在成功操作后会发现`errno == ENOTTY`.)理论上,`strtol()`可以将`"1"转换为`1`并设置` errno`到一个非零值,这将是合法的但变态(并成功). (4认同)
  • 不同意“即使函数成功,库也可以将 errno 设置为非零值”。C11 §7.5 3 讨论了这一点,但这不适用于 `strtol()`,因为“如果 errno 的使用没有记录在函数的描述中”,而 `strtol()` 则这样做。`if (temp == str || *temp != '\0' || errno == ERANGE)` 就足够了。IMO `if (temp == str || *temp != '\0' || errno)` 更好,因为它捕获了一些 ID 扩展。不需要`(*val == LONG_MIN || *val == LONG_MAX)`。 (3认同)
  • 无论“strtol”是否返回“LONG_MIN”/“LONG_MAX”,无条件检查“errno == ERANGE”是否有原因?(由于您在评论中给出的原因,库函数可能会在成功时设置“errno”。) (2认同)
  • @JonathanLeffler 同意“EINVAL”,因此建议“temp == str ||” *温度!= '\0' || errno` - 我认为我们对此非常同意。然而评论是关于需要 `*val == LONG_MIN || *val == LONG_MAX`,鉴于 _other_ `errno` 可能性,该功能不会得到增强。如果 `errno == ERANGE` 为 true,那么即使 `*val == LONG_MIN || *val == LONG_MAX` 在某些独角兽机器上为 false,`strtol()` 仍应被视为失败。 (2认同)

chr*_*ris 19

你快到了.temp本身不会为null,但如果整个字符串被转换,它将指向一个空字符,因此您需要取消引用它:

if (*temp != '\0')
Run Code Online (Sandbox Code Playgroud)

  • 需要额外的检查来处理溢出和解析空字符串.见Jonathan Leffler的回答. (3认同)

chu*_*ica 7

如何成功检测 strtol 的错误?

static long parseLong(const char * str) {
    int base = 0;
    char *endptr;
    errno = 0;
    long val = strtol(str, &endptr, base);
Run Code Online (Sandbox Code Playgroud)

标准 C 库指定/支持的 3 个测试:

  1. 有转换完成吗?

     if (str == endptr) puts("No conversion.");
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在范围内?

     // Best to set errno = 0 before the strtol() call.
     else if (errno == ERANGE) puts("Input out of long range.");
    
    Run Code Online (Sandbox Code Playgroud)
  3. 拖尾垃圾?

     else if (*endptr) puts("Extra junk after the numeric text.");
    
    Run Code Online (Sandbox Code Playgroud)

成功

    else printf("Success %ld\n", val);
Run Code Online (Sandbox Code Playgroud)

输入类似str == NULL或非base0、[2 到 36] 是未定义行为。各种实现(C 库的扩展)通过errno. 我们可以添加第四个测试。

    else if (errno) puts("Some implementation error found.");
Run Code Online (Sandbox Code Playgroud)

或者结合测试errno == ERANGE


示例简洁代码还利用了常见的实现扩展。

long my_parseLong(const char *str, int base, bool *success) {
    char *endptr = 0;
    errno = 0;
    long val = strtol(str, &endptr, base);
   
    if (success) {
      *success = endptr != str && errno == 0 && endptr && *endptr == '\0';
    }
    return val;
}
Run Code Online (Sandbox Code Playgroud)