O algoritmo strcasecmp é falho?

34

Estou tentando reimplementar a strcasecmpfunção em C e notei o que parece ser uma inconsistência no processo de comparação.

De man strcmp

A função strcmp () compara as duas seqüências s1 e s2. O código do idioma não é levado em consideração (para uma comparação com reconhecimento do código do idioma, consulte strcoll (3)). Retorna um número inteiro menor que, igual a ou maior que zero se s1 for encontrado, respectivamente, menor que, para corresponder ou maior que s2.

De man strcasecmp

A função strcasecmp () executa uma comparação de bytes por bytes das seqüências s1 e s2, ignorando o caso dos caracteres. Retorna um número inteiro menor que, igual a ou maior que zero se s1 for encontrado, respectivamente, menor que, para corresponder ou maior que s2.

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

Dada essa informação, não entendo o resultado do seguinte código:

#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

Parece que, se o caractere atual em s1for uma letra, ele sempre será convertido em minúsculas, independentemente de o caractere atual s2ser uma letra ou não.

Alguém pode explicar esse comportamento? A primeira e a terceira linhas não deveriam ser idênticas?

Agradeço antecipadamente!

PS:
Estou usando o gcc 9.2.0Manjaro.
Além disso, quando compilo com a -fno-builtinbandeira, recebo:

-30
2
2
2

Eu acho que é porque o programa não usa as funções otimizadas do gcc, mas a questão permanece.

Haltarys
fonte
2
Adicione outro caso de teste ao seu conjunto: printf("%i\n", strcasecmp("a", "_"));presumivelmente, isso deve ter o mesmo resultado que printf("%i\n", strcasecmp("A", "_"));Mas isso significa que uma dessas duas chamadas que não diferenciam maiúsculas de minúsculas vai discordar de sua contraparte que diferencia maiúsculas de minúsculas.
anton.burger 21/02
Parece que a descrição a que strcasecmpvocê está se referindo não é precisa. Mais detalhes nas respostas votadas.
Jabberwocky
9
É a única coisa que faz sentido. Uma função que diz A < _ && a > _ && A == acausaria muitos problemas.
ikegami 21/02
Além: "Estou tentando reimplementar a função strcasecmp em C" -> Embora o código não seja mostrado, compare "como se" unsigned char. C17 / 18 "Tratamento de strings <string.h>" -> "Para todas as funções desta subcláusula, cada caractere deve ser interpretado como se tivesse o tipo unsigned char". Isso faz diferença quando os charvalores estão fora do intervalo ASCII de 0 a 127.
chux - Restabelece Monica
11
Sobre as diferenças nas saídas com integrados e sem: Ambos dizem o mesmo, pois seus resultados são identicamente <0 e> 0, e você não tem um exemplo para == 0. Mas você pode ver os algoritmos: alguns dos valores retornados são as diferenças do primeiro caractere não igual.
the busybee

Respostas:

31

O comportamento está correto.

De acordo com a str\[n\]casecmp()especificação POSIX :

Quando a LC_CTYPEcategoria do código do idioma usado for do código do POSIX, essas funções deverão se comportar como se as seqüências de caracteres tivessem sido convertidas em minúsculas e, em seguida, realizada uma comparação de bytes. Caso contrário, os resultados não serão especificados.

Isso também faz parte da NOTAS seção da página de homem Linux :

O padrão POSIX.1-2008 diz sobre estas funções:

Quando a categoria LC_CTYPE do código do idioma que está sendo usado for do código do idioma POSIX, essas funções deverão se comportar como se as seqüências de caracteres tivessem sido convertidas em minúsculas e executada uma comparação de bytes. Caso contrário, os resultados não serão especificados.

Por quê?

Como o @HansOlsson apontou em sua resposta , fazer comparações sem distinção entre maiúsculas e minúsculas entre apenas letras e permitir que todas as outras comparações tenham seus resultados "naturais", como foi feito strcmp(), quebraria a classificação.

Se 'A' == 'a'(a definição de uma comparação sem distinção entre maiúsculas e minúsculas) '_' > 'A'e '_' < 'a'( e os resultados "naturais" no conjunto de caracteres ASCII) não puderem ser verdadeiros.

Andrew Henle
fonte
Fazer comparações sem distinção entre maiúsculas e minúsculas entre apenas letras não resultaria '_' > 'A' && '_' < 'a'; não parece ser o melhor exemplo.
Asteroids With Wings
11
@AsteroidsWithWings Esses são os caracteres usados ​​na pergunta. E se, 'a' == 'A' por definição , se você fizer uma comparação entre os valores "naturais" de 'a', 'A'e '_', não poderá fazer uma comparação sem distinção entre maiúsculas e minúsculas entre 'A'e 'a'para obter igualdade e obter resultados de classificação consistentes.
Andrew Henle
Não estou contestando isso, mas o contra-exemplo específico que você forneceu não parece ser relevante.
Asteroids With Wings
@AsteroidsWithWings Go através do exercício mental de construir uma árvore binária de 'a', 'A'e '_', passando por todos os 6 ordens de inserção na árvore, e comparando os resultados das as-especificadas "letras sempre minúscula" para a questão da proposta "só converter letras quando é uma comparação letra a letra ". Por exemplo, usando o último algoritmo e começando com '_', 'a'e 'A'acabe em lados opostos da árvore, no entanto, eles são definidos como iguais. O algoritmo "apenas converter letras para minúsculas nas comparações letra-letra" é quebrado e esses três caracteres mostram isso.
Andrew Henle
Certo, sugiro demonstrar isso na resposta, porque no momento basta ressaltar que " '_' > 'A' e '_' < 'a'não podem ser verdadeiras" sem nos dizer por que deveríamos ter pensado que seria. (Essa é uma tarefa para quem responde, não para um milhão de leitores.)
Asteroids With Wings
21

Outros links, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html para strcasecmp, dizem que a conversão para minúsculas é o comportamento correto (pelo menos no código do idioma POSIX).

A razão para esse comportamento é que, se você usar strcasecmp para classificar uma matriz de seqüências, é necessário obter resultados razoáveis.

Caso contrário, se você tentar classificar "A", "C", "_", "b" usando, por exemplo, qsort, o resultado dependerá da ordem das comparações.

Hans Olsson
fonte
3
Caso contrário, se você tentar classificar "A", "C", "_", "b" usando, por exemplo, qsort, o resultado dependerá da ordem das comparações. Bom ponto. Essa é provavelmente a razão pela qual o POSIX especifica o comportamento.
Andrew Henle
6
Mais concretamente, você precisa de uma ordem total para classificação, o que não seria o caso se você definir a comparação como na pergunta (já que não seria transitiva).
Dukeling
8

Parece que, se o caractere atual em s1 for uma letra, ele sempre será convertido em minúsculas, independentemente de o caractere atual em s2 ser uma letra ou não.

Está correto - e é o que a strcasecmp()função deve fazer! É uma POSIXfunção, e não parte do CPadrão, mas das " Especificações de Base do Grupo Aberto, Edição 6 ":

No código do idioma POSIX, strcasecmp () e strncasecmp () devem se comportar como se as seqüências de caracteres tivessem sido convertidas em minúsculas e, em seguida, uma comparação de bytes realizada. Os resultados não são especificados em outros locais.

Aliás, esse comportamento também é relevante para a _stricmp()função (conforme usado no Visual Studio / MSCV):

A função _stricmp compara ordinariamente string1 e string2 após converter cada caractere em minúsculas e retorna um valor indicando o relacionamento.

Adrian Mole
fonte
2

O código decimal ASCII para Aé 65para _é 95e para aé 97, então strcmp()está fazendo o que deveria fazer. Lexicograficamente falando _é menor ae maior que A.

strcasecmp()considerará Acomo sendo a* e, como aé maior que _a saída, também está correto.

* O padrão POSIX.1-2008 diz sobre essas funções (strcasecmp () e strncasecmp ()):

Quando a categoria LC_CTYPE do código do idioma que está sendo usado for do código do idioma POSIX, essas funções deverão se comportar como se as seqüências de caracteres tivessem sido convertidas em minúsculas e executada uma comparação de bytes. Caso contrário, os resultados não serão especificados.

Fonte: http://man7.org/linux/man-pages/man3/strcasecmp.3.html

anastaciu
fonte
3
O ponto de vista do OP é que Aé "maior" do que _ao comparar sem distinção entre maiúsculas e minúsculas e se pergunta por que o resultado não é o mesmo que quando se compara com distinção entre maiúsculas e minúsculas.
anton.burger 21/02
6
A declaração Since strcasecmp () `não faz distinção entre maiúsculas e minúsculas; considerará A como sendo um` é uma dedução inválida. Uma rotina que não diferencia maiúsculas de minúsculas pode tratar todas as letras maiúsculas como se fossem minúsculas, tratar todas as letras minúsculas como se fossem letras maiúsculas ou tratar cada letra maiúscula como igual à sua letra minúscula correspondente e vice-versa, mas ainda compará-las para caracteres sem letra com seus valores brutos. Esta resposta não indica um motivo para preferir nenhuma dessas possibilidades (o motivo correto pelo qual a documentação diz que usa letras minúsculas).
Eric Postpischil em 21/02
@EricPostpischil O padrão POSIX.1-2008 diz sobre essas funções (strcasecmp () e strncasecmp ()): Quando a categoria LC_CTYPE do código do idioma usado for do código do POSIX, essas funções deverão se comportar como se as cadeias de caracteres tivessem sido convertidas em minúsculas e, em seguida, uma comparação de bytes realizada. Caso contrário, os resultados não serão especificados.
anastaciu