Зачем использовать "strlen30 ()" вместо "strlen ()"? - PullRequest
7 голосов
/ 27 июля 2011

Я прочитал и задался вопросом об исходном коде sqlite

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

Зачем использовать strlen30() вместо strlen() (в string.h) ??

Ответы [ 3 ]

3 голосов
/ 27 июля 2011

Сообщение о фиксации , которое пришло с этим изменением, сообщает:

[793aaebd8024896c] часть регистрации [c872d55493] Никогда не используйте strlen ().Используйте наш собственный внутренний sqlite3Strlen30 (), который гарантированно никогда не переполняет целое число.Дополнительные явные приведения, чтобы избежать ложных предупреждений.(CVS 6007) (пользователь: drh branch: trunk)

2 голосов
/ 27 июля 2011

(это мой ответ от Почему переопределение strlen как цикл + вычитание? , но оно было закрыто)


Я не могу сказать вам причину, по которой им пришлось его повторно реализовать, и почему они выбрали 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) : Это означает, что арифметика без знака не переполняется, поскольку результат, который не может быть представлен результирующим целым числом без знака type уменьшается по модулю на число, которое на единицу больше наибольшего значения, которое может быть представлено результирующим целым числом без знака тип

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

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

Пока что до переполнения.

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

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

Глядя на 18,1 :

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

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

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

Больше никаких гарантий по поводу ptrdiff_t не предоставляется. Затем в Приложении E (все еще в ISO / IEC 9899: TC2) дается минимальная величина для длинного целого со знаком, но не максимум:

#define LONG_MAX +2147483647

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

#define INT_MAX +32767



Краткое описание

  1. Обычно ptrdiff_t не больше signed long, что не меньше 32 бит.
  2. int определяется как минимум 16 бит.
  3. Следовательно, вычитание двух указателей может дать результат, который не вписывается в int вашей платформы.
  4. Мы помним сверху, что для подписанных типов результат, который не соответствует, приводит к неопределенному поведению.
  5. 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 бит и предотвратить переполнение).1119 *

1 голос
/ 27 июля 2011

Сообщение о фиксации CVS гласит:

Никогда не используйте strlen (). Используйте наш собственный внутренний sqlite3Strlen30 (), который гарантированно никогда не переполняет целое число. Дополнительные явные приведения, чтобы избежать ложных предупреждений. (CVS 6007)

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

...