Понимание функции strcmp в gnu libc - PullRequest
0 голосов
/ 29 октября 2018

Вот функция strcmp, которую я нашел в glibc :

int
STRCMP (const char *p1, const char *p2)
{
  const unsigned char *s1 = (const unsigned char *) p1;
  const unsigned char *s2 = (const unsigned char *) p2;
  unsigned char c1, c2;

  do
    {
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0')
        return c1 - c2;
    }
  while (c1 == c2);

  return c1 - c2;
}

Это довольно простая функция, когда тело while инициирует c1 и c2 со значениями *s1 и *s2 и продолжается до тех пор, пока c1 не станет равным nul или значениям c1 и c2 равны, затем возвращает разницу между c1 и c2.

Что я не понял, так это использование переменных s1 и s2. Я имею в виду, кроме того факта, что они unsigned char они также const как 2 аргумента p1 и p2, так почему бы просто не использовать p1 и p2 внутри тела while и бросить их? Делает ли в этом случае использование этих 2 дополнительных переменных функцию более оптимизированной? потому что здесь есть та же самая функция для FreeBSD, которую я нашел на github :

int
strcmp(const char *s1, const char *s2)
{
    while (*s1 == *s2++)
        if (*s1++ == '\0')
            return (0);
    return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1));
}

В их версии они даже не удосужились использовать дополнительные переменные.

Заранее спасибо за ваши ответы.

PS: прежде чем спрашивать здесь, я искал в интернете об этом конкретном факте, но ничего не получил.

Я также хотел бы знать, есть ли какая-то конкретная причина, по которой glibc использовал эти дополнительные переменные вместо преобразования параметров p1 и p2 непосредственно в while.

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

Что я не понял, так это использование переменных s1 и s2. Я имею в виду, кроме того факта, что они являются беззнаковыми символами, они также являются константами, такими как 2 аргумента p1 и p2, так почему бы просто не использовать p1 и p2 внутри тела while и приводить их?

для удобства чтения; чтобы нам, людям, было проще поддерживать код.

Если вы посмотрите на исходные коды glibc, код стремится к удобочитаемости, а не к кратким выражениям. Кажется, это хорошая политика, потому что она поддерживает ее актуальность и активность (активно поддерживается) уже более 30 лет.

Делает ли в этом случае использование этих 2 дополнительных переменных функцию более оптимизированной?

Нет, совсем нет.

Я также хотел бы знать, есть ли какая-то конкретная причина, по которой glibc использовал эти дополнительные переменные вместо того, чтобы приводить параметры p1 и p2 непосредственно внутри while.

Только для чтения.

Авторы знают, что используемый компилятор C должен прекрасно оптимизировать этот код. (И это легко доказать, просто взглянув на сгенерированные компилятором кода. Для GCC вы можете использовать опцию -S, или вы можете использовать binutils 'objdump -d для проверки объектного файла или двоичный исполняемый файл.)

Обратите внимание, что приведение к unsigned char требуется по тем же причинам, что и для isspace(), isalpha() и так далее: сравниваемые коды символов должны рассматриваться как unsigned char для правильные результаты.

0 голосов
/ 29 октября 2018

Вы правы, конечно. Одного из приведений должно быть достаточно. Особенно, если приведен указатель, приведение извлеченного значения не допускается.


Вот x86-64, скомпилированный с gcc -O3 для ненужного приведения:

STRCMP:
.L4:
        addq    $1, %rdi
        movzbl  -1(%rdi), %eax
        addq    $1, %rsi
        movzbl  -1(%rsi), %edx
        testb   %al, %al
        je      .L7
        cmpb    %dl, %al
        je      .L4
        subl    %edx, %eax
        ret
.L7:
        movzbl  %dl, %eax
        negl    %eax
        ret

и вот тот, без лишнего приведения:

STRCMP:
.L4:
        addq    $1, %rdi
        movzbl  -1(%rdi), %eax
        addq    $1, %rsi
        movzbl  -1(%rsi), %edx
        testb   %al, %al
        je      .L7
        cmpb    %dl, %al
        je      .L4
        subl    %edx, %eax
        ret
.L7:
        movzbl  %dl, %eax
        negl    %eax
        ret

Они идентичны


Однако есть одна ошибка, которая в настоящее время в основном представляет исторический интерес. Если char это со знаком и подписанное представление будет не дополнением до двух,

*(const unsigned char *)p1

и

(unsigned char)*p1

не эквивалент. Первая интерпретирует битовую комбинацию, а вторая преобразует значение, используя арифметику по модулю. Это представляет исторический интерес, так как даже GCC не поддерживает любую архитектуру , которая не имеет представления со знаком 2 дополнения. И это компилятор с большинством портов.

...