Как работает реализация glibc strlen()

strlen() от K & R занимает всего несколько строк.

int strlen(char *s)
{
    char *p = s;
    while (*p != '\0')
        p++;
    return p - s;
}

Но glibc версия намного дольше. Для простоты я удалил все комментарии и 64-битную реализацию, извлеченная версия strlen() выглядит так:

size_t strlen(const char *str)
{
    const char *char_ptr;
    const unsigned long int *longword_ptr;
    unsigned long int longword, magic_bits, himagic, lomagic;

    for (char_ptr = str; ((unsigned long int) char_ptr 
             & (sizeof (longword) - 1)) != 0; ++char_ptr)
       if (*char_ptr == '\0')
           return char_ptr - str;

    longword_ptr = (unsigned long int *) char_ptr;

    himagic = 0x80808080L;
    lomagic = 0x01010101L;

    for (;;)
    { 
        longword = *longword_ptr++;

        if (((longword - lomagic) & himagic) != 0)
        {
            const char *cp = (const char *) (longword_ptr - 1);

            if (cp[0] == 0)
                return cp - str;
            if (cp[1] == 0)
                return cp - str + 1;
            if (cp[2] == 0)
                return cp - str + 2;
            if (cp[3] == 0)
                return cp - str + 3;
        }
    }
}

С помощью очень полезного комментария (нажмите здесь), я получил большую часть того, как это работает. Вместо проверки байта '\0' by by byte, glibc strlen() проверяет каждое слово (4 байта в 32-разрядной машине, 8 байт в 64-разрядной машине). Таким образом, когда строка относительно длинная, производительность может быть улучшена.

Он проверяет первые несколько символов, читая байты байтом, пока char_ptr не будет выровнен по границе longword. Затем он использует цикл, чтобы проверить, имеет ли longword все байты с именами всех нулей. Если есть, проверьте, какой байт, и верните результат.

Часть, которую я не получаю, как это проверить, если один байт в longword является all-zeros?

if (((longword - lomagic) & himagic) != 0)

Я могу построить значение longword 0x81818181, которое может сделать 0x81818181 - 0x01010101) & 0x80808080 не равным 0, но нет всех нулевых байтов.

Связано ли это с тем, что значения ASCII варьируются от 0 до 127, поэтому 0x81 недействителен ASCII? Но я не думаю, что стандарт C заставляет использовать ASCII.

Ответы

Ответ 1

Я понял это. Не могу поверить, что это просто, я провел последние полчаса на нем.

Хорошо, что проверка

if (((longword - lomagic) & himagic) != 0)

передает значения, такие как 0x81818181 pass, потому что, если он пройдет, следующий тест на каждый байт не будет возвращаться, так как нет байта с нулевым нулем. Таким образом, цикл может продолжить проверку следующего longword.


Алгоритм проверки основан на Определите, имеет ли слово нулевой байт

unsigned int v; 
bool hasZeroByte = ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F);

В 2 дополнение - 0x01010101 имеет тот же эффект с + 0xFEFEFEFF. Разница заключается в том, что glibc не имеет v & 0x7F7F7F7F, что гарантирует, что байты в слове имеют самый старший бит 0. Это предотвращает такие примеры, как 0x81818181, но glibc опускает его, потому что ему не нужно пропускать его, как указано ранее. Проверка правильная, если она не пропустит ни одного слова, которое имеет все нулевые байты.