Является ли алгоритм strcasecmp некорректным? - PullRequest
34 голосов
/ 21 февраля 2020

Я пытаюсь переопределить функцию strcasecmp в C, и я заметил несоответствие в процессе сравнения.

С man strcmp

Функция strcmp () сравнивает две строки s1 и s2. Локаль не учитывается (сравнение с учетом локали смотрите в strcoll (3)). Он возвращает целое число меньше, равно или больше нуля, если найдено s1, соответственно, меньше, для соответствия или больше s2.

С man strcasecmp

Функция strcasecmp () выполняет побитовое сравнение строк s1 и s2, игнорируя регистр символов. Он возвращает целое число меньше, равно или больше нуля, если найдено s1, соответственно, меньше, для соответствия или больше s2.

int strcmp(const char *s1, const char *s2);
int strcasecmp(const char *s1, const char *s2);

Учитывая эту информацию, я не понимаю результат следующего кода:

#include <stdio.h>
#include <string.h>

int main()
{
    // ASCII values
    // 'A' = 65
    // '_' = 95
    // 'a' = 97

    printf("%i\n", strcmp("A", "_"));
    printf("%i\n", strcmp("a", "_"));
    printf("%i\n", strcasecmp("A", "_"));
    printf("%i\n", strcasecmp("a", "_"));
    return 0;
}

Ouput:

-1  # "A" is less than "_"
1   # "a" is more than "_"
2   # "A" is more than "_" with strcasecmp ???
2   # "a" is more than "_" with strcasecmp

Похоже, что если текущий символ в s1 является буквой, он всегда преобразуется в нижний регистр, независимо от того, является ли текущий символ в s2 буквой или нет.

Может кто-нибудь объяснить такое поведение? Разве первая и третья строки не должны совпадать?

Заранее спасибо!

PS:
Я использую gcc 9.2.0 на Манджаро.
Кроме того, когда я компилирую с флагом -fno-builtin, который я получаю вместо:

-30
2
2
2

Я думаю, это потому, что программа не использует оптимизированные функции g cc, но вопрос остается.

Ответы [ 4 ]

31 голосов
/ 21 февраля 2020

Поведение правильное.

За спецификацию POSIX str\[n\]casecmp() :

Когда категория LC_CTYPE используемой локали из в локали POSIX эти функции должны вести себя так, как будто строки были преобразованы в нижний регистр, а затем выполняется сравнение байтов. В противном случае результаты не уточняются.

Это также часть NOTES раздела Linux man-страницы :

Стандарт POSIX.1-2008 говорит об этих функциях:

Когда категория LC_CTYPE используемой локали относится к локали POSIX, эти функции должны вести себя так, как если бы строки были преобразованы в нижний регистр и затем выполняется сравнение байтов. В противном случае результаты не уточняются.

Почему?

Как указал @HansOlsson в своем ответе , проводит сравнения без учета регистра между только буквами и разрешением всех других сравнений иметь свои «естественные» результаты, как это было сделано в strcmp(), будет нарушена сортировка.

Если 'A' == 'a' (определение сравнения без учета регистра), тогда '_' > 'A' и '_' < 'a' ("естественные" результаты в наборе символов ASCII) не могут быть оба истинными.

21 голосов
/ 21 февраля 2020

Другие ссылки, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html для strcasecmp говорят, что преобразование в нижний регистр - это правильное поведение (по крайней мере, в локали POSIX).

Причина в том, что если вы используете strcasecmp для сортировки массива строк, необходимых для получения разумных результатов.

В противном случае, если вы попытаетесь отсортировать «A», «C», «_», «b», например, с помощью qsort результат будет зависеть от порядка сравнений.

8 голосов
/ 21 февраля 2020

Похоже, что если текущий символ в s1 является буквой, он всегда преобразуется в нижний регистр, независимо от того, является ли текущий символ в s2 буквой или нет.

Это правильно - и это то, что strcasecmp() функция должна делать! Это функция POSIX, а не часть C стандарта, но из " Базовые спецификации открытых групп, выпуск 6 ":

В POSIX locale, strcasecmp () и strncasecmp () должны вести себя так, как будто строки были преобразованы в нижний регистр, а затем выполняется сравнение байтов. Результаты не указаны в других локалях.

Кстати, это поведение также относится к функции _stricmp() (как используется в Visual Studio / MSCV):

Функция _stricmp обычно сравнивает строку1 и строку2 после преобразования каждого символа в нижний регистр и возвращает значение, указывающее их отношение.

2 голосов
/ 21 февраля 2020

Десятичный код ASCII для A равен 65 для _ равен 95, а для a равен 97, поэтому strcmp() делает то, что должен делать. Лексикографически говоря, _ меньше a и больше A.

strcasecmp() будет рассматривать A как a*, а поскольку a больше _ вывод также правильный.

* Стандарт POSIX.1-2008 говорит об этих функциях (strcasecmp () и strncasecmp ()):

Когда категория LC_CTYPE локали будучи используемым из языка POSIX, эти функции должны вести себя так, как если бы строки были преобразованы в нижний регистр, а затем выполнено сравнение байтов. В противном случае результаты не уточняются.

Источник: http://man7.org/linux/man-pages/man3/strcasecmp.3.html

...