检测执行字符集的字母是否连续。

7
在这段代码中,使用了 switch 语句将字母转换为连续的值。通常情况下,优化编译器执行这个任务不如在 switch 前使用简单连续数字条件来得好。我该如何检测所用的执行字符集,并且/或者得出这些字母是连续的,以便将其替换为简单条件语句?
static long digit_value(char c)
{
    if (c >= '0' && c <= '9')
        return (c-'0');

    switch(c) {
    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;
    case 'g':
    case 'G':
        return 16;
    case 'h':
    case 'H':
        return 17;
    case 'i':
    case 'I':
        return 18;
    case 'j':
    case 'J':
        return 19;
    case 'k':
    case 'K':
        return 20;
    case 'l':
    case 'L':
        return 21;
    case 'm':
    case 'M':
        return 22;
    case 'n':
    case 'N':
        return 23;
    case 'o':
    case 'O':
        return 24;
    case 'p':
    case 'P':
        return 25;
    case 'q':
    case 'Q':
        return 26;
    case 'r':
    case 'R':
        return 27;
    case 's':
    case 'S':
        return 28;
    case 't':
    case 'T':
        return 29;
    case 'u':
    case 'U':
        return 30;
    case 'v':
    case 'V':
        return 31;
    case 'w':
    case 'W':
        return 32;
    case 'x':
    case 'X':
        return 33;
    case 'y':
    case 'Y':
        return 34;
    case 'z':
    case 'Z':
        return 35;
    default:
        break;
    }

    return -1;
}

一个char字面量的字符集是否保证为ASCII? - mkrieger1
2
尽管该语言不要求,但除非您需要在EBCDIC环境中进行移植,否则可以指望字母是连续的。 - Barmar
仅排除EBCDIC是否足够?(对我来说,这是一种传奇,因为我从未使用过这种字符集)。 - Roberto Caboni
1
字符集可能存在间隙,但据我所知它们需要被排序。使用检查'z'-'a'==25来检测间隙是否存在会起到作用吗? - Gerhardh
我见过现代GCC和Clang将具有大量连续条目的开关转换为数组查找,所以坦白地说,如果您的编译器不能做到这一点,我的第一个反应是“它可以,您只是没有将优化设置调高”,我的第二个反应是“好吧,也许它不能,但实际上,几乎所有情况要么是您不应该进行优化,除了明确表达您的意图以使编译器优化尽可能低,要么就是您可以在您的利基性能关键用例中假定一个字符集。” - mtraceur
说了这么多,我认为这个问题是合理的,我强烈理解它背后的动机。 - mtraceur
4个回答

7
我该如何检测使用的执行字符集,并/或得出字母是否连续?
在编译时,可以测试它们全部。为了简单起见,省略了'a-z'。
static_assert(
  'A' == ('B' - 1) &&
  'B' == ('C' - 1) && 'C' == ('D' - 1) && 'D' == ('E' - 1) && 'E' == ('F' - 1) && 'F' == ('G' - 1) && 'G' == ('H' - 1) && 'H' == ('I' - 1) && 'I' == ('J' - 1) && 'J' == ('K' - 1) && 'K' == ('L' - 1) && 'L' == ('M' - 1) && 'M' == ('N' - 1) && 'N' == ('O' - 1) && 'O' == ('P' - 1) && 'P' == ('Q' - 1) && 'Q' == ('R' - 1) && 'R' == ('S' - 1) && 'S' == ('T' - 1) && 'T' == ('U' - 1) && 'U' == ('V' - 1) && 'V' == ('W' - 1) && 'W' == ('X' - 1) && 'X' == ('Y' - 1) &&
  'Y' == ('Z' - 1), "Dinosaur: not continuous A-Z");

static int digit_value(char c) {
  if (c >= '0' && c <= '9') return c - '0';
  if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
  return -1;
}

其他恐龙测试.


或者使用速度较慢但高度便携的:

static int digit_value(char c) {
  static const char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  const char *p = strchr(base36, (unsigned char) c);
  if (p && *p) {
    return (int) (p - base36);
  }
  return -1;
}

或者可能需要一个大的 #if 语句?
#if ('A' == ('B' - 1) && 'B' == ('C' - 1) && 'C' == ('D' - 1) && 'D' == ('E' - 1) && 'E' == ('F' - 1) && 'F' == ('G' - 1) && 'G' == ('H' - 1) && 'H' == ('I' - 1) && 'I' == ('J' - 1) && 'J' == ('K' - 1) && 'K' == ('L' - 1) && 'L' == ('M' - 1) && 'M' == ('N' - 1) && 'N' == ('O' - 1) && 'O' == ('P' - 1) && 'P' == ('Q' - 1) && 'Q' == ('R' - 1) && 'R' == ('S' - 1) && 'S' == ('T' - 1) && 'T' == ('U' - 1) && 'U' == ('V' - 1) && 'V' == ('W' - 1) && 'W' == ('X' - 1) && 'X' == ('Y' - 1) && 'Y' == ('Z' - 1))

static int digit_value(char c) {
  if (c >= '0' && c <= '9') return c - '0';
  if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
  return -1;
}

#else

static int digit_value(char c) {
  static const char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  const char *p = strchr(base36, (unsigned char) c);
  if (p && *p) {
    return (int) (p - base36);
  }
  return -1;
}

#endif

或者……如果 UCHAR_MAX 不太大并且关注速度,则可以创建一个查找表并跳过顺序考虑。
#include <limits.h>
int digit_value(char c) {
  unsigned char val[UCHAR_MAX] = {['0'] = 1, ['1'] = 2, ['2'] = 3, ['3'] = 4,
      ['4'] = 5, ['5'] = 6, ['6'] = 7, ['7'] = 8, ['9'] = 10, 
      ['A'] = 11, ['B'] = 12, ['C'] = 13, ['D'] = 14, ['E'] = 15, ...
      ['a'] = 11, ['b'] = 12, ['c'] = 13, ['d'] = 14, ['e'] = 15, ...
  };
  return val[(unsigned char) c] - 1;
}

代码必须允许任何类型的字符集,但是static_assert禁止非连续的字符集。 - hdante
3
只需使用 if ('A' == ('B' - 1) && 'B' == ('C' - 1) ... 'Y' == ('Z' - 1)) 和第一个 digit_value(),否则请使用第二个带有其 strchr()digit_value(char c)。编译器会进行优化。 - chux - Reinstate Monica
@chux-ReinstateMonica 很好,我不知道我是开心还是难过,但我会尝试。 - hdante
正如我在答案中所写的,我的唯一担忧是static_assert断言是标准的“新”功能,它不太可能在旧系统中得到支持。 - Roberto Caboni
1
@RobertoCaboni 对于C99,已经存在替代方案:是否有一个满足C99标准的static_assert替代品? 通常可以通过将数组大小设置为-1来模拟失败。 - chux - Reinstate Monica
显示剩余2条评论

1

您可以像@chux所回答的那样为条件编译编写适当的测试。但是,如果您需要支持具有不连续字母的字符集,则需要支持该字符集的实现,表查找比您在问题中提出的内容更有效率。因此,您可以考虑使用它来代替维护两个单独的实现。例如:

static long digit_value(char c) {
    static const long dvs[UCHAR_MAX + 1] = {
      [0] = 1, [1] = 2, [2] = 3, [3] = 4, [5] = 6, [7] = 8, [8] = 9, [9] = 10,
      ['A'] = 11, ['B'] = 12, ['C'] = 13, ['D'] = 14, ['E'] = 15, ['F'] = 16, ['G'] = 17,
      ['H'] = 18, ['I'] = 19, ['J'] = 20, ['K'] = 21, ['L'] = 22, ['M'] = 23, ['N'] = 24,
      ['O'] = 25, ['P'] = 26, ['Q'] = 27, ['R'] = 28, ['S'] = 29, ['T'] = 30, ['U'] = 31,
      ['V'] = 32, ['W'] = 33, ['X'] = 34, ['Y'] = 35, ['Z'] = 36,
      ['a'] = 11, ['b'] = 12, ['c'] = 13, ['d'] = 14, ['e'] = 15, ['f'] = 16, ['g'] = 17,
      ['h'] = 18, ['i'] = 19, ['j'] = 20, ['k'] = 21, ['l'] = 22, ['m'] = 23, ['n'] = 24,
      ['o'] = 25, ['p'] = 26, ['q'] = 27, ['r'] = 28, ['s'] = 29, ['t'] = 30, ['u'] = 31,
      ['v'] = 32, ['w'] = 33, ['x'] = 34, ['y'] = 35, ['z'] = 36
    };

    return dvs[(unsigned char) c] - 1;
}

请注意,基本执行字符集中的字符,包括所有十进制数字和大写和小写拉丁字母,保证具有非负整数值。这在表的初始化器方面有所简化。还要注意,未明确初始化的元素将被默认初始化为零,最终通过从表格值中减去1将其转换为-1返回值。

也许应该将 long dvs[] 改为 char dvs[] - chux - Reinstate Monica
也许,@chux。我只是将表元素类型与返回类型进行了匹配(这些类型我从OP的代码中获取),知道表格会占用比它实际需要的更多的空间。我不确定在OP的目标环境中选择表元素类型的性能影响,但将其更改为“char”可能会提供大小和速度上的优势。 - John Bollinger

0
首先我要说的是,我会选择预处理器解决方案,因为编译器的字符集是我想在编译时了解的信息。 _static_assert 将是一种优雅的解决方案,但由于它只在 C11 标准中引入,真正意义上的古老编译器不太可能支持它。
我会使用常见的预处理指令进行检查,即通过一系列 #if 的链式结构,确保一个字符的表示连续到前一个字符。

一个简单的运行时检查

编译时检查已经在其他回答中得到了很好的解决,因此我想建议一种简单的运行时方法来排除EBCDIC字符集:假设字符集可能是EBCDIC或ASCII

通过排除EBCDIC,我们可以假设使用了ASCII字符集,因此字符是连续的。

只需要考虑两个简单的EBCDIC特性:

  • 具有非连续十进制表示的字母是众所周知的(例如'j'!= 'i'+1
  • 所有百个EBCDIC变体中的字母应该有共同的十进制表示,如IBM页面不变字符集所建议的那样

所以:

static long digit_value(char c)
{
    if (c >= '0' && c <= '9')
        return (c-'0');

    if ( 'j' == 'i'+1 ) //if it's not EBCDIC 
    {
        if( c >= 'a' && c <= 'z' )
        {
             return 10 + c - 'a';
        }
        else if ( c >= 'A' && c <= 'Z' )
        {
            return 10 + c - 'A';
        }
    }

    return -1;
}

如果是EBCDIC字符集,则返回错误代码;如果是ASCII字符集,则通过简单的条件语句确保在小写和大写字符中都返回范围为[10-35]的值。


0

为了可移植性和最高效性,请使用查找表

const char table[128] = {
                         ['0'] = 0,  ['1'] = 1, /* ... */ ['9'] = 9, 
                         ['a'] = 10, ['A'] = 10,
                         ['b'] = 11, ['B'] = 11,
                         ['c'] = 12, ['C'] = 12,
                         ['d'] = 13, ['D'] = 13,
                         ['e'] = 14, ['E'] = 14,
                         ['f'] = 15, ['f'] = 15,
                         ['g'] = 16, ['g'] = 16,
                         ['h'] = 17, ['H'] = 17,
                         ['i'] = 18, ['I'] = 18,
                         ['j'] = 19, ['J'] = 19,
                         /* etc etc */
};

int get_value(char ch)
{
    return table[ch & 0x7f];
}

以及生成的代码:

get_value:
        movsx   rdi, dil
        movsx   eax, BYTE PTR table[rdi]
        ret

1
['i'] = 18, ['J'] = 18 中有拼写错误,请改为 ['I'] = 18, ['J'] = 18。建议使用类似于 table[256] 的数据结构,并在 return table[ch]; 中添加负索引保护。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接