strcspn - ¿Cómo funciona la implementación de strchr?
strchr() c++ (4)
Creo que esto es en realidad un defecto en la definición del estándar C de la función strchr()
. (Estaré encantado de que me demuestren que estoy equivocado). (Respondiendo a los comentarios, es discutible si realmente es un defecto; en mi humilde opinión sigue siendo un diseño deficiente. Se puede usar de manera segura, pero es muy fácil de usar de manera insegura.)
Esto es lo que dice el estándar C:
char *strchr(const char *s, int c);
La función strchr localiza la primera aparición de c (convertida en un char ) en la cadena señalada por s . El carácter nulo de terminación se considera parte de la cadena.
Lo que significa que este programa:
#include <stdio.h>
#include <string.h>
int main(void) {
const char *s = "hello";
char *p = strchr(s, ''l'');
*p = ''L'';
return 0;
}
a pesar de que define cuidadosamente el puntero al literal de cadena como un puntero al char
const
, tiene un comportamiento indefinido, ya que modifica el literal de cadena. gcc, al menos, no advierte sobre esto, y el programa muere con una falla de segmentación.
El problema es que strchr()
toma un argumento const char*
, lo que significa que promete no modificar los datos a los que apunta, pero devuelve un char*
simple char*
, que permite al llamante modificar los mismos datos.
Aquí hay otro ejemplo; no tiene un comportamiento indefinido, pero modifica silenciosamente un objeto cualificado por const
sin ningún lanzamiento (que, pensándolo mejor, creo que tiene un comportamiento indefinido):
#include <stdio.h>
#include <string.h>
int main(void) {
const char s[] = "hello";
char *p = strchr(s, ''l'');
*p = ''L'';
printf("s = /"%s/"/n", s);
return 0;
}
Lo que significa, creo, (para responder a su pregunta) que una implementación en C de strchr()
tiene que emitir su resultado para convertirlo de const char*
a char*
, o hacer algo equivalente.
Esta es la razón por la cual C ++, en uno de los pocos cambios que realiza en la biblioteca estándar de C, reemplaza a strchr()
con dos funciones sobrecargadas del mismo nombre:
const char * strchr ( const char * str, int character );
char * strchr ( char * str, int character );
Por supuesto que C no puede hacer esto.
Una alternativa hubiera sido reemplazar strchr
por dos funciones, una que toma un const char*
y devuelve un const char*
, y otra que toma un char*
y devuelve un char*
. A diferencia de C ++, las dos funciones tendrían que tener nombres diferentes, tal vez strchr
y strcchr
.
(Históricamente, const
se agregó a C después de que strchr()
ya se había definido. Esta fue probablemente la única forma de mantener strchr()
sin romper el código existente).
strchr()
no es la única función de biblioteca estándar de C que tiene este problema. La lista de funciones afectadas (creo que esta lista está completa pero no la garantizo) es:
void *memchr(const void *s, int c, size_t n);
char *strchr(const char *s, int c);
char *strpbrk(const char *s1, const char *s2);
char *strrchr(const char *s, int c);
char *strstr(const char *s1, const char *s2);
(todo declarado en <string.h>
) y:
void *bsearch(const void *key, const void *base,
size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
(declarado en <stdlib.h>
). Todas estas funciones toman un puntero a datos const
que apuntan al elemento inicial de una matriz y devuelven un puntero no const
a un elemento de esa matriz.
Intenté escribir mi propia implementación del método strchr ().
Ahora se ve así:
char *mystrchr(const char *s, int c) {
while (*s != (char) c) {
if (!*s++) {
return NULL;
}
}
return (char *)s;
}
La última línea originalmente fue
return s;
Pero esto no funcionó porque s es const. Descubrí que tiene que haber este reparto (char *), pero honestamente no sé lo que estoy haciendo allí :( ¿Alguien puede explicarlo?
La palabra clave const
significa que el parámetro no se puede modificar.
No pudo devolver s
directamente porque s
está declarado como const char *s
el tipo de retorno de la función es char *
. Si el compilador le permitiera hacerlo, sería posible anular la restricción const
.
Agregar una conversión explícita a char*
le dice al compilador que sabes lo que estás haciendo (aunque, como explicó Eric, sería mejor si no lo hicieras).
ACTUALIZACIÓN: Por el bien del contexto, estoy citando la respuesta de Eric, ya que parece haberla eliminado:
No deberías estar modificando s ya que es un const const *.
En su lugar, defina una variable local que represente el resultado de tipo char * y use eso en lugar de s en el cuerpo del método.
La práctica de devolver los punteros no constantes a los datos const de las funciones no modificables es en realidad un modismo bastante utilizado en el lenguaje C. No siempre es bonito, pero está bastante bien establecido.
La realidad aquí es simple: strchr
en sí misma es una operación no modificadora. Sin embargo, necesitamos la funcionalidad strchr
tanto para cadenas constantes como para cadenas no constantes, que también propagarían la constancia de la entrada a la constancia de la salida. Ni C ni C ++ proporcionan un soporte elegante para este concepto, lo que significa que en ambos idiomas tendrá que escribir dos funciones prácticamente idénticas para evitar correr riesgos con la corrección constante.
En C ++, puedes usar la sobrecarga de funciones declarando dos funciones con el mismo nombre
const char *strchr(const char *s, int c);
char *strchr(char *s, int c);
En C, no tiene una sobrecarga de funciones, por lo que para aplicar completamente la corrección constante en este caso, tendría que proporcionar dos funciones con nombres diferentes , algo como
const char *strchr_c(const char *s, int c);
char *strchr(char *s, int c);
Aunque en algunos casos esto podría ser lo correcto, en general es (y con razón) considerado demasiado engorroso e involucrado por los estándares de C. Puede resolver esta situación de una manera más compacta (aunque más arriesgada) implementando una sola función
char *strchr(const char *s, int c);
que devuelve el puntero no constante a la cadena de entrada (utilizando un lanzamiento en la salida, exactamente como lo hizo). Tenga en cuenta que este enfoque no viola ninguna regla del idioma, aunque proporciona a la persona que llama los medios para violarla. Al desechar la constancia de los datos, este enfoque simplemente delega la responsabilidad de observar la constricción correcta de la función en sí misma a la persona que llama. Siempre que la persona que llama se dé cuenta de lo que está pasando y recuerde "jugar bien", es decir, utiliza un puntero cualificado por constantes para señalar datos constantes, cualquier brecha temporal en el muro de constreñida creada por dicha función se repara de manera instantánea.
Veo este truco como un enfoque perfectamente aceptable para reducir la duplicación innecesaria de códigos (especialmente en ausencia de sobrecarga de funciones). La biblioteca estándar lo usa. Usted tampoco tiene ninguna razón para evitarlo, suponiendo que comprende lo que está haciendo.
Ahora, en cuanto a su implementación de strchr
, me parece extraño desde el punto de vista estilístico. Yo usaría el encabezado de ciclo para recorrer en iteración el rango completo en el que estamos operando (la cadena completa), y usar el interno para detectar la condición de terminación temprana
for (; *s != ''/0''; ++s)
if (*s == c)
return (char *) s;
return NULL;
Pero cosas así son siempre una cuestión de preferencia personal. Alguien podría preferir simplemente
for (; *s != ''/0'' && *s != c; ++s)
;
return *s == c ? (char *) s : NULL;
Algunos podrían decir que modificar los parámetros de la función dentro de la función es una mala práctica.
El valor de retorno de la función debe ser un puntero constante para un carácter:
strchr
acepta un const char*
y debería devolver const char*
también. Está devolviendo una no constante que es potencialmente peligrosa ya que el valor de retorno apunta a la matriz de caracteres de entrada (la persona que llama puede esperar que el argumento constante permanezca constante, pero es modificable si alguna parte de la misma se devuelve como un puntero char *
).
El valor de retorno de la función debe ser NULL si no se encuentra ningún carácter coincidente:
También se supone que strchr
devuelve NULL
si no se encuentra el carácter buscado. Si devuelve un valor no NULL cuando no se encuentra el carácter, o s en este caso, la persona que llama (si cree que el comportamiento es el mismo que strchr) podría asumir que el primer carácter en el resultado realmente coincide (sin el valor de retorno NULL no hay manera de saber si hubo una coincidencia o no).
(No estoy seguro de si eso es lo que pretendías hacer).
Aquí hay un ejemplo de una función que hace esto:
Escribí y realicé varias pruebas sobre esta función; Agregué algunos controles de cordura realmente obvios para evitar posibles choques:
const char *mystrchr1(const char *s, int c) {
if (s == NULL) {
return NULL;
}
if ((c > 255) || (c < 0)) {
return NULL;
}
int s_len;
int i;
s_len = strlen(s);
for (i = 0; i < s_len; i++) {
if ((char) c == s[i]) {
return (const char*) &s[i];
}
}
return NULL;
}