Подтвердить что ты не робот

Почему переопределить strlen как цикл + вычитание?

Вдохновленный этот вопрос о следующем коде SQLite3:

 static int strlen30(const char *z){
    const char *z2 = z;
    while( *z2 ){ z2++; }
    return 0x3fffffff & (int)(z2 - z);
 }

который сопровождается сообщением , говорящим, что эта функция помогает с переполнениями int.

Меня особенно интересует эта часть:

 const char *z2 = z;
 while( *z2 ){ z2++; }

для меня этот цикл продвигается z2 до тех пор, пока z2 не укажет на нулевой ограничитель. Затем z2-z выводит длину строки.

Почему бы не использовать strlen() для этой части и переписать вот так:

return 0x3fffffff & (int)(strlen(z));

Зачем использовать вычитание цикла + вместо strlen()? Что может сделать цикл + вычитание, что strlen() не может?

4b9b3361

Ответ 1

Почему переопределить strlen как цикл + вычитание?

Я подозреваю, что реальный ответ заключается в том, что программисту это нравилось, но другое потенциальное обоснование/рационализация заключается в том, что цикл является встроенным (независимо от того, является ли сам strlen30), тогда как во многих системах strlen (например, Linux/GCC). Если подавляющее большинство строк пустые или короткие (несмотря на "специальную" обработку длинных строк), то это может дать небольшой балл производительности для общего случая. Одной только этой возможности может быть достаточно, чтобы получить ключ-нажатие программиста. Для более длинных строк я ожидал бы, что библиотека strlen будет в целом оптимальной (учитывая отсутствие знаний о конкретной длине строк).

Некоторые системы могут даже не извлекать выгоду из этой вставки, поскольку strlen предоставляет ее собственный или встроенный/внелинейный гибрид с быстрой встроенной проверкой для пустого, одного char, возможно двух- char затем вызовы.

Ответ 2

Я не могу сказать вам причину, по которой они должны были повторно реализовать его, и почему они выбрали int, а не size_t в качестве возвращаемого типа. Но о функции:

/*
 ** Compute a string length that is limited to what can be stored in
 ** lower 30 bits of a 32-bit signed integer.
 */
static int strlen30(const char *z){
    const char *z2 = z;
    while( *z2 ){ z2++; }
    return 0x3fffffff & (int)(z2 - z);
}



Стандартные ссылки на усечение, типы, переполнение

В стандарте говорится (ISO/IEC 14882: 2003 (E)) 3.9.1 Основные типы, 4.:

Неподписанные целые числа, объявленные без знака, должны подчиняться законам арифметики по модулю 2 n где n - количество бит в представлении значений этого конкретного размера целых чисел. 41)

...

41): это означает, что беззнаковая арифметика не переполняется, потому что результат, который не может быть представлен результирующим целым без знака тип уменьшается по модулю число, которое больше одного наибольшего значения, которое может быть представлено полученным целым без знака Тип

Эта часть стандарта не определяет поведение переполнения для целых чисел со знаком. Если мы посмотрим на 5. Выражения, 5.:

Если во время оценки выражения результат не определяется математически или нет в диапазоне представляемых значений для его типа, поведение undefined, если такое выражение не является постоянным выражением (5.19), и в этом случае программа плохо сформирована. [Примечание: большинство существующих реализаций С++ игнорируют целое число переполняется. Обработка деления на ноль, формирование остатка с использованием делителя нуля и всей плавающей запятой исключения различаются между машинами и обычно регулируются библиотечной функцией. ]

До сих пор для переполнения.

Как для вычитания двух указателей на элементы массива, 5.7 Аддитивные операторы, 6.:

Когда два указателя на элементы одного и того же объекта массива вычитаются, результатом является разность индексов двух элементов массива. Тип результата - это определенный тип интегрированного интегрального типа; этот тип должен быть того же типа, что и ptrdiff_t в заголовке (18.1). [...]

Глядя на 18.1:

Содержимое совпадает с заголовком библиотеки стандартного C stddef.h

Итак, давайте посмотрим на стандарт C (у меня есть только экземпляр C99), 7.17 Общие определения:

  • Типы, используемые для size_t и ptrdiff_t, не должны иметь целочисленный ранг преобразования больше, чем у подписанного long int, если реализация не поддерживает объекты достаточно большой, чтобы сделать это необходимым.

Никакой гарантии не было сделано о ptrdiff_t. Затем приложение E (все еще в ISO/IEC 9899: TC2) дает минимальную величину для подписанного длинного int, но не максимум:

#define LONG_MAX +2147483647

Теперь каковы максимальные значения для int, возвращаемого типа для sqlite - strlen30()? Давайте пропустим цитату из С++, которая снова перенаправляет нас на C-стандарт, и мы увидим в C99, Приложение E, минимальный максимум для int:

#define INT_MAX +32767



Сводка о части усечения

  • Обычно ptrdiff_t не больше signed long, который не меньше 32 бит.
  • int определяется как минимум 16 бит длиной.
  • Следовательно, вычитание двух указателей может дать результат, который не вписывается в int вашей платформы.
  • Мы помним, что для подписанных типов результат, который не подходит, дает поведение undefined.
  • strlen30 выполняет побитовое или по вычитанию-вычитанию-результат:

          | 32 bit                         |
ptr_diff  |10111101111110011110111110011111| // could be even larger
&         |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
          ----------------------------------
=         |00111101111110011110111110011111| // truncated

Это предотвращает деактивацию поведения путем усечения результата вычитания указателя до максимального значения 3FFFFFFF 16= 1073741823 10.

Я не уверен, почему они выбрали именно это значение, потому что на большинстве машин только знаковый бит указывает подпись. Возможно, имело смысл в сравнении со стандартом, чтобы выбрать минимальный INT_MAX, но 1073741823 действительно немного странный, не зная больше деталей (хотя он, конечно же, отлично делает то, что говорится в комментарии выше их функции: обрезать до 30 бит и предотвратить переполнение).



"Почему бы не использовать strlen() для этой части"

и перепишите его следующим образом:

return 0x3fffffff & (int)(strlen(z));

Я предполагаю, что они хотели избежать потенциальной косвенности. Другим преимуществом может быть меньшее количество зависимостей от стандартной библиотеки, что может быть полезно, если вы пишете не-размещенное приложение.

Btw, как следует из приведенных выше ссылок, (int)(strlen(z)) может давать поведение undefined, если максимум для ptrdiff_t > INT_MAX, поэтому (int)(0x3fffffff & strlen(z)) будет лучше.