【问题标题】:atoi vs atol vs strtol vs strtoul vs sscanfatoi vs atol vs strtol vs strtoul vs sscanf
【发布时间】:2014-04-04 14:30:23
【问题描述】:

我试图从正在解析的命令行中找出哪个函数最适合将十进制、十六进制或八进制数转换为 int 最好 — 事先不知道输入。

然后目标是使用一个函数来识别不同类型的输入并将其分配给它的整数 (int) 值,然后可以这样使用:

./a.out 23 0xC4 070

可以打印

23
196 /*hexadecimal*/
56  /*octal*/

我能看到的唯一问题是解析以找出十进制整数和八进制之间的差异。

附带问题,这对于将字符串转换为整数以供使用是否稳定?

【问题讨论】:

  • atoi()atol() 在错误恢复方面非常有限; sscanf() 太复杂了;使用strtol()strtoul()
  • "事先不知道输入" 你的意思是你不知道数字的各自基数吗?没有一般的方法可以从数字的数字推断出它的基数。例如,“70”可以是基数 8 或基数 10 或基数 16。
  • 没有关于表示含义的信息,没有人可以解释它。通常我们使用文化民间传说来传达这些信息(例如“八进制前导 0”);如果你想要一些与此不同的东西,你会 a) 让每个人都感到不安,并且 b) 必须编写自己的代码。
  • 如果您将0 作为base 参数传递给strtolstrtoul,它们将检测输入的基数,但仅限于可以判断的范围内,使用 tesseract 提到的前缀。所以如果你想让70被视为八进制,你需要使用070,然后strtoul("070", NULL, 0)会返回56(十进制)。我很确定 strtoul("C4", NULL, 0) 将返回 196,尽管缺少 0x 前缀,但这只是因为 C 泄露了它是十六进制的事实。
  • @MikeHolt 您应该将其发布为答案。恐怕"C4" 不会转换为十六进制,但前缀是必需的。规则与源代码中的整数文字相同,这是有道理的。

标签: c


【解决方案1】:

哪个函数最适合将十进制、十六进制或八进制数转换为int 最好 (?)

要将此类文本转换为int,建议long strtol(const char *nptr, char **endptr, int base); 在转换为int 时进行额外测试(如果需要)。

使用0 作为base 来评估转向转换中的早期字符为基数10、16 或8。
@Mike Holt

Convert text per:
Step 1: Optional whitespaces like `' '`, tab, `'\n'`, ... .
Step 2: Optional sign: `'-'` or `'+'`.
Step 3:
  0x or 0X followed by hex digits--> hexadecimal  
  0 --> octal  
  else --> decimal  

示例代码

#include <errno.h>
#include <limits.h>
#include <stdlib.h>

int mystrtoi(const char *str) {
  char *endptr;
  errno = 0;
  //                                   v--- determine conversion base
  long long_var = strtol(str, &endptr, 0);
  //   out of range   , extra junk at end,  no conversion at all   
  if (errno == ERANGE || *endptr != '\0' || str == endptr) {
    Handle_Error();
  }

  // Needed when `int` and `long` have different ranges
  #if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
  if (long_var < INT_MIN || long_var > INT_MAX) {
    errno = ERANGE;
    Handle_Error();
  }
  #endif

  return (int) long_var;
}

atoi vs atol vs strtol vs strtoul vs sscanf ... to int

atoi()
专业人士:非常简单。
亲:转换为int
Pro:在 C 标准库中。
专业人士:快速。
缺点:超出范围的错误,未定义的行为@chqrlie
缺点:既不处理十六进制也不处理八进制。

atol()
优点:简单。
Pro:在 C 标准库中。
专业人士:快速。
缺点:转换为 long,而不是大小可能不同的 int
缺点:如果出现超出范围的错误,未定义的行为
缺点:既不处理十六进制也不处理八进制。

strtol()
优点:简单。
Pro:在 C 标准库中。
优点:良好的错误处理。
专业人士:快速。
Pro:可以处理二进制文件。 (基数 2 到基数 36)
缺点:转换为 long,而不是大小可能不同的 int

strtoul()
优点:简单。
Pro:在 C 标准库中。
优点:良好的错误处理。
专业人士:快速。
Pro:可以处理二进制文件。
---:不抱怨负数。
缺点:转换为 unsigned long,而不是大小可能不同的 int

sscanf(..., "%i", ...)
Pro:在 C 标准库中。
专业版:转换为 int
---:中间的复杂性。
缺点:可能很慢。
缺点:OK 错误处理(未定义溢出)。

所有人都受益于locale 设置。 §7.22.1.4 6 “在“C”语言环境之外,可以接受其他特定于语言环境的主题序列形式。”


其他学分:
@Jonathan Lefflererrno 测试 ERANGEatoi() 仅十进制,讨论 errno 多线程问题。
@Marian 速度问题。
@Kevin 图书馆的包容性。


对于转换shortsigned char等,请考虑strto_subrange()

【讨论】:

  • 反对sscanf的主要观点是它与其他人相比非常慢。
  • 你说'errno可能会因其他并发进程而改变'。假设 s/processes/threads/ 与您的意思相匹配,那么在线程环境中,errno 是特定于线程的,因此不存在(不应该)问题。 ISO/IEC 9899:2011 第 7.5 节错误&lt;errno.h&gt; 说:errno 扩展为具有 int 类型和线程本地存储持续时间的可修改左值,其值设置为正错误数几个库函数。脚注 201 解释说 errno 不必是对象的标识符(例如,它可以是 *errno())。
  • @ArturCzajka 1) 如果转换使long 超出int 的范围,(int) strtol (__nptr, (char **) NULL, 10); 会丢失信息 2) (char **) NULL 会丢失有关转换停止位置的信息。 3) 出错时,实现不需要遵循(int) strtol (__nptr, (char **) NULL, 10);,即使它在您今天使用的编译器上这样做,出错时,行为未定义。 strtol()没有这些缺点。
  • 你可能想强调atoiatol的问题,而不是简洁的短语Con: No error handling,即:引用C标准:如果结果的值无法表示,则行为未定义。
  • @chqrlie 好的。帖子已编辑。
【解决方案2】:

如果您关心错误情况,只考虑strtol()strtoul()(或&lt;stdlib.h&gt; 中的strtoll()strtoull(),或者也许strtoimax()strtoumax() 中的&lt;inttypes.h&gt;)是明智的.如果您不关心溢出时的错误条件,则可以使用它们中的任何一个。 atoi()atol()sscanf() 都不能让您控制值是否溢出。此外,atoi()atol() 都不提供对十六进制或八进制输入的支持(因此实际上您无法使用它们来满足您的要求)。

请注意,调用strtoX() 函数并不完全是微不足道的。你必须在调用它们之前将errno 设置为0,并传递一个指针以获取结束位置,并仔细分析以了解发生了什么。请记住,这些函数的所有可能返回值都是有效输出,但其中一些也可能表示无效输入——errno 和结束指针可帮助您区分它们。

如果您需要在使用strtoll() 读取值后转换为int,您可以对照&lt;limits.h&gt; 中定义的范围检查返回值的范围(存储在long long 中) intINT_MININT_MAX

有关详细信息,请参阅我的回答:Correct usage of strtol()

请注意,这些函数都不会告诉您使用了哪种转换。您需要自己分析字符串。奇怪的注释:你知道 C 源代码中没有十进制 0 吗?当你写0时,你写的是一个八进制常数(因为它的第一个数字是0)。这件琐事没有实际后果。

【讨论】:

  • 当输入的值大于(或小于)整数类型时。返回值被限制在类型支持的范围的末尾,但errno == ERANGE 表示发生了溢出。如果您有 32 位 long 值,即使使用了所有数字,这也可能发生在值 5,000,000,000(减去逗号)上。
  • 钳制到“类型支持的范围的末尾”有一个未讨论的细微差别。对于strtoul(),转换后应用前导-"-1" 变为 ULONG_MAX,未限制为 0。
  • @chux-ReinstateMonica:是的,但什么时候会出现问题?假设 sizeof(unsigned int) != sizeof(unsigned long) 并且您想要一个 unsigned int 值。您使用strtoul() 处理字符串"-1"。正如您所说,strtoul() 返回ULONG_MAX,与UINT_MAX 相比,发现更大,而钳位返回UINT_MAX — 这就是预期的结果,不是吗?所以这不是问题。如果sizeof(unsigned int) == sizeof(unsigned long),则没有问题。我不清楚它什么时候会引起问题。您不应将无符号转换器用于有符号值,反之亦然。
  • "-1"UINT_MAX 的值可以被视为与您的 returned value is clamped to the end of the range supported by the type 的矛盾,因为它不是 0。之前考虑过 strtol() - 的意义夹紧,strtoul() 是后记,所以不设置 errno
  • @chux-ReinstateMonica:我认为有一个简单的替代方案——更好的——论点,使用除-1 之外的负数,例如-2 其中sizeof(unsigned int) &lt; sizeof(unsigned long)。我认为,真正的strtoui() 会返回UINT_MAX - 1,但钳位会返回UINT_MAX。我需要(认真)思考这个问题。也许使用strtoul() 实现不同大小的stroui() 需要strtoui() 跳过空格,跟踪负号,检查符号后面的数字,将字符串传递给strtoul() 不带符号,监视范围返回,应用标志本身。
猜你喜欢
  • 2014-08-17
  • 2011-09-18
  • 1970-01-01
  • 2012-07-27
  • 2010-12-09
  • 2017-06-20
  • 2012-08-07
相关资源
最近更新 更多