C 和 C++ 是否保证 [a-f] 和 [A-F] 字符的 ASCII?

Does C and C++ guarantee the ASCII of [a-f] and [A-F] characters?

我正在查看以下代码来测试十六进制数字并将其转换为整数。该代码有点聪明,因为它利用了大写字母和小写字母之间的差异是 32,也就是第 5 位。因此代码执行了一个额外的 OR,但节省了一个 JMP 和两个 CMPs.

static const int BIT_FIVE = (1 << 5);
static const char str[] = "0123456789ABCDEFabcdef";

for (unsigned int i = 0; i < COUNTOF(str); i++)
{
    int digit, ch = str[i];

    if (ch >= '0' && ch <= '9')
        digit = ch - '0';
    else if ((ch |= BIT_FIVE) >= 'a' && ch <= 'f')
        digit = ch - 'a' + 10;
    ...
}

C 和 C++ 是否保证 [a-f] 和 [A-F] 字符的 ASCII 或值?在这里,保证意味着上下字符集将始终相差一个可以用位表示的常量值(对于上面的技巧)。如果不是,标准对它们有何评价?

(抱歉 C 和 C++ 标签。我对这两种语言在该主题上的立场感兴趣)。

无法保证特定值,但您不必关心,因为您的软件可能永远不会遇到以这种方式与 ASCII 不兼容的系统。假设 space 总是 32 而 A 总是 65,这在现代世界中工作得很好。

C 标准仅保证字母 A-Z 和 a-z 存在并且它们适合单个字节。

确实保证0-9是连续的。

In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.

理由

世界上有很多字符编码。如果您关心可移植性,您可以让您的程序可移植到不同的字符集,或者您可以选择一种字符集以在任何地方使用(例如 Unicode)。我将继续为您粗略地分类大多数现有的字符编码:

  1. 与ISO/IEC646兼容的单字节字符编码。数字0-9和字母A-Z和a-z始终占据相同的位置。

  2. 多字节字符编码(Big5、Shift JIS、基于 ISO 2022)。在这些编码中,您的程序可能 已经损坏 如果您愿意,您需要花时间修复它。但是,解析数字仍会按预期工作。

  3. Unicode 编码。数字 0-9 和字母 A-Z、a-z 始终占据相同的位置。您可以自由地使用代码点或代码单元,如果您使用的代码点低于 128(您是),您将获得相同的结果。 (您使用的是 UTF-7 吗?不,您应该只将它用于电子邮件。

  4. EBCDIC。数字和字母分配的值与其在 ASCII 中的值不同,但是,0-9 和 A-F、a-f 仍然是连续的。即便如此,您的代码在 EBCDIC 系统上 运行 的可能性基本上为零。

所以这里的问题是:您是否认为未来会发明假设的第五种选择,比 Unicode 更不兼容/更难使用?

你关心 EBCDIC 吗?

我们可以整天想出奇怪的系统...假设 CHAR_BIT 是 11,或者 sizeof(long) = 100,或者假设我们使用一个补码算法,或者 malloc() 总是 returns NULL,或者假设您显示器上的像素排列成六边形网格。假设您的浮点数不是 IEEE 754,假设您所有的数据指针大小都不同。归根结底,这并没有使我们更接近在实际现代系统上编写工作软件的目标(偶尔例外)。

不,不是。

C 标准保证十进制数字和大小写字母存在,以及许多其他字符。它还保证十进制数字是连续的,例如 '0' + 9 == '9',并且 基本执行字符集 的所有成员都具有非负值。它特别 not 保证字母是连续的。 (有关所有详细信息,请参阅 C 标准的 N1570 草案,第 5.2.1 节;基本字符非负的保证在 6.2.5p3 中,在类型 char 的讨论中.)

'a' .. 'f''A' .. 'F' 具有连续代码的假设几乎肯定是合理的。在 ASCII 和所有基于 ASCII 的字符集中,26 个小写字母和 26 个大写字母都是连续的。即使在 ASCII 的唯一重要竞争对手 EBCDIC 中,整个字母表也不连续,但字母 'a' ..'f''A' .. 'F' 是(EBCDIC 在 'i''j' 之间、'r' 和 [=23= 之间、'I''J' 之间以及 'R''S').

但是,设置表示的第 5 位会将大写字母转换为小写字母的假设对 EBCDIC 无效。在 ASCII 中,小写字母和大写字母的代码相差 32;在 EBCDIC 中,它们相差 64.

这种为节省一两条指令而进行的位操作在标准库的一部分或已知对性能至关重要的代码中可能是合理的。恕我直言,基于 ASCII 的字符集的隐式假设至少应通过注释明确说明。一个 256 元素的静态查找 table 可能会更快,但要牺牲少量的额外存储空间。

为了获得最大的便携性、清晰度和速度,我建议使用一个简单的开关:

int hex_digit_value(char x)
{
    switch (x)
    {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    case 'A':
    case 'a': return 10;
    case 'B':
    case 'b': return 11;
    case 'C':
    case 'c': return 12;
    case 'D':
    case 'd': return 13;
    case 'E':
    case 'e': return 14;
    case 'F':
    case 'f': return 15;
    default: return -1;
    }
}

clang -O1 -S 将其转换为简单的 table 查找:

    addl    $-48, %edi
    cmpl    , %edi
    ja  .LBB0_2

    movslq  %edi, %rax
    movl    .Lswitch.table(,%rax,4), %eax
    retq
.LBB0_2:
    movl    $-1, %eax
    retq

为了完整起见,这里是生成的查找 table:

.Lswitch.table:
.long   0                       # 0x0
.long   1                       # 0x1
.long   2                       # 0x2
.long   3                       # 0x3
.long   4                       # 0x4
.long   5                       # 0x5
.long   6                       # 0x6
.long   7                       # 0x7
.long   8                       # 0x8
.long   9                       # 0x9
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   10                      # 0xa
.long   11                      # 0xb
.long   12                      # 0xc
.long   13                      # 0xd
.long   14                      # 0xe
.long   15                      # 0xf
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   4294967295              # 0xffffffff
.long   10                      # 0xa
.long   11                      # 0xb
.long   12                      # 0xc
.long   13                      # 0xd
.long   14                      # 0xe
.long   15                      # 0xf