¿Es defectuoso el algoritmo strcasecmp?

34

Estoy tratando de reimplementar la strcasecmpfunción en C y noté lo que parece ser una inconsistencia en el proceso de comparación.

Desde man strcmp

La función strcmp () compara las dos cadenas s1 y s2. La configuración regional no se tiene en cuenta (para una comparación de configuración regional, consulte strcoll (3)). Devuelve un número entero menor, igual o mayor que cero si se encuentra s1, respectivamente, menor que, para que coincida o mayor que s2.

Desde man strcasecmp

La función strcasecmp () realiza una comparación byte por byte de las cadenas s1 y s2, ignorando el caso de los caracteres. Devuelve un número entero menor, igual o mayor que cero si se encuentra s1, respectivamente, menor que, para que coincida o mayor que s2.

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

Dada esta información, no entiendo el resultado del siguiente 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, si el carácter actual en s1es una letra, siempre se convierte en minúsculas, independientemente de si el carácter actual en s2es una letra o no.

¿Alguien puede explicar este comportamiento? ¿No deberían ser idénticas las líneas primera y tercera?

¡Gracias de antemano!

PD:
estoy usando gcc 9.2.0en Manjaro.
Además, cuando compilo con la -fno-builtinbandera me sale en su lugar:

-30
2
2
2

Supongo que es porque el programa no usa las funciones optimizadas de gcc, pero la pregunta sigue siendo.

Haltarys
fuente
2
Agregue otro caso de prueba a su conjunto: printf("%i\n", strcasecmp("a", "_"));presumiblemente, esto debería tener el mismo resultado que printf("%i\n", strcasecmp("A", "_"));Pero eso significa que una de estas dos llamadas que no distinguen entre mayúsculas y minúsculas no estará de acuerdo con su contraparte sensible a mayúsculas y minúsculas.
anton.burger
Parece que la descripción a la strcasecmpque te refieres no es precisa. Más detalles en las respuestas votadas.
Jabberwocky
99
Es lo único que tiene sentido. Una función que dice A < _ && a > _ && A == acausaría tantos problemas.
ikegami
Aparte: "Estoy tratando de reimplementar la función strcasecmp en C" -> Aunque no se muestra el código, asegúrese de comparar "como si" unsigned char. C17 / 18 "Manejo de cadenas <string.h>" -> "Para todas las funciones en esta subcláusula, cada carácter se interpretará como si tuviera el tipo unsigned char". Esto hace la diferencia una vez que los charvalores están fuera del rango ASCII 0-127.
chux - Restablece a Monica
1
Sobre las diferencias en las salidas con incorporados y sin ellos: Ambos dicen lo mismo, ya que sus resultados son idénticamente <0 y> 0, y no tiene un ejemplo para == 0. Pero puede ver brillar los algoritmos: algunos de los valores devueltos son las diferencias del primer carácter no igual.
the busybee

Respuestas:

31

El comportamiento es correcto.

Por la str\[n\]casecmp()especificación POSIX :

Cuando la LC_CTYPEcategoría del entorno local que se utiliza es del entorno local POSIX, estas funciones se comportarán como si las cadenas se hubieran convertido a minúsculas y luego se realizara una comparación de bytes. De lo contrario, los resultados no están especificados.

Eso también es parte de la sección NOTAS de la página de manual de Linux :

El estándar POSIX.1-2008 dice de estas funciones:

Cuando la categoría LC_CTYPE del entorno local que se utiliza es del entorno local POSIX, estas funciones se comportarán como si las cadenas se hubieran convertido a minúsculas y luego se realizara una comparación de bytes. De lo contrario, los resultados no están especificados.

¿Por qué?

Como @HansOlsson señaló en su respuesta , hacer comparaciones que no distingan entre mayúsculas y minúsculas entre letras y permitir que todas las demás comparaciones tengan sus resultados "naturales" tal como se hace strcmp(), interrumpiría la clasificación.

Si 'A' == 'a'(la definición de una comparación entre mayúsculas y minúsculas), entonces '_' > 'A'y '_' < 'a'(los resultados "naturales" en el conjunto de caracteres ASCII) no pueden ser ambos verdaderos.

Andrew Henle
fuente
Hacer comparaciones que no distingan entre mayúsculas y minúsculas entre solo letras no daría como resultado '_' > 'A' && '_' < 'a'; No parece ser el mejor ejemplo.
Asteroides con alas
1
@AsteroidsWithWings Esos son los caracteres utilizados en la pregunta. Y si 'a' == 'A' , por definición , si lo hace una comparación entre los valores "naturales" de 'a', 'A'y '_', que no se puede hacer una comparación entre mayúsculas y minúsculas entre 'A'y 'a'para conseguir la igualdad y obtener ordenar resultados consistentes.
Andrew Henle
No estoy discutiendo eso, pero el contraejemplo específico que proporcionó no parece ser relevante.
Asteroides con alas
@AsteroidsWithWings Realice el ejercicio mental de construir un árbol binario a partir de 'a', 'A'y '_', pasando por los 6 órdenes de inserción en el árbol, y comparando los resultados de las "letras minúsculas" como se especifica con las "letras convertidas únicas" de la pregunta cuando se trata de una comparación letra a letra ". Por ejemplo, usar el último algoritmo y comenzar con '_', 'a'y 'A'terminar en lados opuestos del árbol, pero se definen como iguales. El algoritmo "solo convertir letras a minúsculas en las comparaciones letra-letra" está roto y esos 3 caracteres lo demuestran.
Andrew Henle
Bien, entonces sugiero demostrar eso en la respuesta porque en este momento simplemente salta a señalar que " '_' > 'A' y '_' < 'a'no puede ser verdad" sin decirnos por qué deberíamos haber pensado que sería. (Esa es una tarea para el que responde, no para uno de los millones de lectores.)
Asteroides con alas
21

Otros enlaces, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html para strcasecmp dicen que la conversión a minúsculas es el comportamiento correcto (al menos en la ubicación POSIX).

La razón de ese comportamiento es que si usa strcasecmp para ordenar una serie de cadenas, es necesario obtener resultados razonables.

De lo contrario, si intenta ordenar "A", "C", "_", "b" utilizando, por ejemplo, qsort, el resultado dependerá del orden de las comparaciones.

Hans Olsson
fuente
3
De lo contrario, si intenta ordenar "A", "C", "_", "b" utilizando, por ejemplo, qsort, el resultado dependerá del orden de las comparaciones. Buen punto. Esa es probablemente la razón por la que POSIX especifica el comportamiento.
Andrew Henle
66
Más concretamente, necesita un orden total para la clasificación, lo que no sería el caso si define la comparación como en la pregunta (ya que no sería transitiva).
Dukeling
8

Parece que, si el carácter actual en s1 es una letra, siempre se convierte en minúsculas, independientemente de si el carácter actual en s2 es una letra o no.

Eso es correcto, ¡y es lo strcasecmp()que debería hacer la función ! Es una POSIXfunción, más que parte del CEstándar, pero, de " The Open Group Base Especificaciones, Issue 6 ":

En el entorno local POSIX, strcasecmp () y strncasecmp () se comportarán como si las cadenas se hubieran convertido a minúsculas y luego se realizara una comparación de bytes. Los resultados no se especifican en otras configuraciones regionales.

Por cierto, este comportamiento también es pertinente para la _stricmp()función (como se usa en Visual Studio / MSCV):

La función _stricmp compara ordinariamente string1 y string2 después de convertir cada carácter a minúsculas, y devuelve un valor que indica su relación.

Adrian Mole
fuente
2

El código decimal ASCII para Aes 65para _es 95y para aes 97, por strcmp()lo que está haciendo lo que se supone que debe hacer. Lexicográficamente hablando _es más pequeño entonces ay más grande que A.

strcasecmp()se considerará Acomo a*, y dado que aes más grande que _la salida, también es correcta.

* El estándar POSIX.1-2008 dice de estas funciones (strcasecmp () y strncasecmp ()):

Cuando la categoría LC_CTYPE del entorno local que se utiliza es del entorno local POSIX, estas funciones se comportarán como si las cadenas se hubieran convertido a minúsculas y luego se realizara una comparación de bytes. De lo contrario, los resultados no están especificados.

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

anastaciu
fuente
3
El punto de OP es que Aes "más grande" que _cuando se compara entre mayúsculas y minúsculas, y se pregunta por qué el resultado no es el mismo que cuando se compara entre mayúsculas y minúsculas.
anton.burger
66
La declaración Since strcasecmp () `no distingue entre mayúsculas y minúsculas, considerará que A como a` es una deducción no válida. Una rutina que no distingue entre mayúsculas y minúsculas podría tratar todas las letras mayúsculas como si fueran letras minúsculas, podría tratar todas las letras minúsculas como si fueran letras mayúsculas o podría tratar cada letra mayúscula como igual a su letra minúscula correspondiente y viceversa, pero aún así compararlas a caracteres que no son letras con sus valores sin formato. Esta respuesta no indica una razón para preferir ninguna de esas posibilidades (la razón correcta es que la documentación dice que use minúsculas).
Eric Postpischil
@EricPostpischil El estándar POSIX.1-2008 dice de estas funciones (strcasecmp () y strncasecmp ()): cuando la categoría LC_CTYPE del entorno local que se utiliza es del entorno local POSIX, estas funciones se comportarán como si las cadenas se hubieran convertido a minúscula y luego se realizó una comparación de bytes. De lo contrario, los resultados no están especificados.
Anastaciu