为什么 strtoul 没有按预期工作?

Why strtoul doesn't work as expected?

本文关键字:工作 strtoul 为什么      更新时间:2023-10-16
嗨,我

写了一个小型测试程序来检查我编写的函数如何将字符串(十六进制数)转换为无符号整数,我发现代码的行为因我使用的编译器或系统而异。

我编译了下面的代码:
(1) IDE C++4.3.2 https://ideone.com/LlcNWw
(2) G++ 4.4.7 在 CentOS6 (64 位)
(3) Ubuntu 上的 G++ 4.6.3 12(64 位)
(4) G++ 4.9.3 在 Cygwin(32 位)环境中

正如预期的那样 (1) 和 (4) 返回 AND 它的结果完全正确,因为第一个值"0x210000000"对于 32 位值来说很大......

Error while converting Id (0x210000000).
success

但 (2) 和 (3) 返回

success
success

所以问题是为什么在不同的平台上使用不同的编译器构建相同的简单 C 代码返回相同的结果......以及为什么"strtoul("0x210000000",....)"没有将"errno"设置为"ERANGE",以表示位33到37超出范围。

平台上的更多跟踪 (3) 给出:

Id (0x210000000) as ul = 0x10000000  - str_end  - errno 0.
sucess
Id (0x10000000) as ul = 0x10000000  - str_end  - errno 0.
sucess


   /* strtoul example */
#include <stdio.h>      /* printf, NULL */
#include <stdlib.h>     /* strtoul */
#include <errno.h>
signed int GetIdentifier(const char* idString)
{
  char *str_end;
  int id = -1;
  errno = 0;
  id = strtoul(idString, &str_end, 16);
  if ( *str_end != '' || (errno == ERANGE))
  {
    printf("Error while converting Id (%s).n", idString);
    return -1;
  }
  // Return error if converted Id is more than 29-bit
  if(id > 0x1FFFFFFF)
  {
    printf("Error: Id (%s) should fit on 29 bits (maximum value: 0x1FFFFFFF).n", idString);
    return -1;
  }
  printf("sucessn");
  return id;
}

int main ()
{
  GetIdentifier("0x210000000");
  GetIdentifier("0x10000000");
  return 0;
}

0x210000000大于 32 位,在 32 位系统上,long 通常为 32 位,这意味着您无法使用 strtoul 正确转换字符串。您需要使用strtoull并使用保证至少为 64 位的unsigned long long

当然,long longstrtoull是在 C99 中引入的,因此您可能需要添加例如 -std=c99(或使用C11等更高标准)才能正确构建它。


问题似乎在于,您假设long始终是 32 位,而实际上它被定义为至少 32 位。例如,请参阅此参考,了解标准整数类型的最小位大小。

在某些平台和编译器上,long可以大于 32 位。64位硬件上的Linux是一个典型的平台,其中long更大,即64位,这当然足以适应0x210000000,这导致strtoul不会给出错误。

您的代码也不正确,假设成功的调用不会更改 errno 的值。 根据 Linux errno手册页:

<errno.h>头文件定义了整数变量errno, 由事件中的系统调用和一些库函数设置 错误以指示出了什么问题。 它的价值是显着的 只有调用的返回值指示错误(即,-1 from 大多数系统调用;-1 或大多数库函数中的 NULL );一个 允许成功的函数更改errno

(POSIX 确实对成功调用的errno修改施加了更大的限制,但 Linux 在很多情况下并没有严格遵守 POSIX,毕竟 GNU 的 Not Unix......

strtoul手册页指出:

strtoul()函数返回转换结果 或者,如果有前导减号,则否定 表示为无符号值的转换,除非原始值 (非否定)值将溢出;在后一种情况下,strtoul() 返回 ULONG_MAX 并将 errno 设置为 ERANGE 。 一模一样 对于 strtoull() 的保留(使用 ULLONG_MAX 而不是 ULONG_MAX )。

除非strtoul返回ULONG_MAX,否则调用strtoulerrno的值是不确定的。