Actualmente estoy trabajando en una biblioteca escrita en C. Muchas funciones de esta biblioteca esperan una cadena como char*
o const char*
en sus argumentos. Comencé con esas funciones siempre esperando la longitud de la cadena size_t
para que no se requiera una terminación nula. Sin embargo, al escribir pruebas, esto resultó en el uso frecuente de strlen()
, así:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Confiar en el usuario para que pase cadenas terminadas correctamente conduciría a un código menos seguro, pero más conciso y (en mi opinión) legible:
libFunction("I hope there's a null-terminator there!");
Entonces, ¿cuál es la práctica sensata aquí? ¿Hacer que la API sea más complicada de usar, pero obligar al usuario a pensar en su entrada o documentar el requisito de una cadena terminada en nulo y confiar en la persona que llama?
CreateFile
toma unLPTCSTR lpFileName
parámetro como entrada. No se espera ninguna longitud de la cadena de la persona que llama. De hecho, el uso de cadenas terminadas en NUL está tan arraigado que la documentación ni siquiera menciona que el nombre del archivo debe estar terminado en NUL (pero, por supuesto, debe estarlo).LPSTR
tipo dice que las cadenas pueden tener terminación NUL, y si no , eso se indicará en la especificación asociada. Entonces, a menos que se indique específicamente lo contrario, se espera que tales cadenas en Win32 terminen en NUL.StringCbCat
por ejemplo, solo el destino tiene un búfer máximo, lo que tiene sentido. La fuente sigue siendo una cadena C común terminada en NUL. Quizás podría mejorar su respuesta aclarando la diferencia entre un parámetro de entrada y un parámetro de salida . Los parámetros de salida siempre deben tener una longitud máxima de búfer; Los parámetros de entrada generalmente están terminados en NUL (hay excepciones, pero es raro en mi experiencia).En C, el idioma es que las cadenas de caracteres están terminadas en NUL, por lo que tiene sentido cumplir con la práctica común; en realidad, es relativamente poco probable que los usuarios de la biblioteca tengan cadenas sin terminación NUL (ya que necesitan trabajo adicional para imprimir usando printf y uso en otro contexto). Usar cualquier otro tipo de cadena no es natural y probablemente sea relativamente raro.
Además, bajo las circunstancias, su prueba me parece un poco extraña, ya que para funcionar correctamente (usando strlen), en primer lugar, está asumiendo una cadena terminada en NUL. Debería probar el caso de cadenas sin terminación NUL si desea que su biblioteca trabaje con ellas.
fuente
Su argumento de "seguridad" realmente no es válido. Si no confías en el usuario para que te entregue una cadena terminada en nulo cuando eso es lo que documentaste (y cuál es "la norma" para C simple), tampoco puedes confiar en la longitud que te darán (que probablemente lo usen
strlen
como lo están haciendo si no lo tienen a mano, y eso fallará si la "cadena" no era una cadena en primer lugar).Sin embargo, existen razones válidas para exigir una longitud: si desea que sus funciones funcionen en subcadenas, posiblemente sea mucho más fácil (y eficiente) pasar una longitud que hacer que el usuario haga un poco de magia de copia de un lado a otro para obtener el byte nulo en el lugar correcto (y corre el riesgo de errores ocasionales en el camino).
Ser capaz de manejar codificaciones donde los bytes nulos no son terminaciones, o ser capaz de manejar cadenas que tienen nulos incrustados (a propósito) puede ser útil en algunas circunstancias (depende de lo que hagan exactamente sus funciones).
También es útil poder manejar datos no terminados en nulo (matrices de longitud fija).
En resumen: depende de lo que esté haciendo en su biblioteca y de qué tipo de datos espera que manejen sus usuarios.
Posiblemente también haya un aspecto de rendimiento en esto. Si su función necesita conocer de antemano la longitud de la cadena y espera que sus usuarios al menos ya conozcan esa información, hacer que la pasen (en lugar de calcularla) podría reducir algunos ciclos.
Pero si su biblioteca espera cadenas de texto ASCII simples y ordinarias, y no tiene restricciones de rendimiento insoportables y una muy buena comprensión de cómo sus usuarios interactuarán con su biblioteca, agregar un parámetro de longitud no parece una buena idea. Si la cadena no se termina correctamente, es probable que el parámetro de longitud sea igual de falso. No creo que ganes mucho con eso.
fuente
No. Las cadenas siempre tienen terminación nula por definición, la longitud de la cadena es redundante.
Los datos de caracteres no terminados en nulo nunca deben llamarse una "cadena". Procesarlo (y arrojar longitudes) generalmente debe encapsularse dentro de una biblioteca y no formar parte de la API. Requerir la longitud como parámetro solo para evitar llamadas strlen () es probable que sea una optimización prematura.
Confiar en el llamador de una función API no es inseguro ; El comportamiento indefinido está perfectamente bien si no se cumplen las condiciones previas documentadas.
Por supuesto, una API bien diseñada no debería contener trampas y debería facilitar su uso correcto. Y esto solo significa que debe ser lo más simple y directo posible, evitando redundancias y siguiendo las convenciones del lenguaje.
fuente
Siempre debes mantener tu longitud alrededor. Por un lado, sus usuarios pueden desear contener NULL en ellos. Y en segundo lugar, no olvide que
strlen
es O (N) y requiere tocar todo el caché de bye de cadena. Y en tercer lugar, facilita el paso de subconjuntos; por ejemplo, podrían dar menos de la longitud real.fuente
strlen
en una prueba de bucle.)Debe distinguir entre pasar una cadena y pasar un búfer .
En C, las cadenas son tradicionalmente terminadas en NUL. Es completamente razonable esperar esto. Por lo tanto, generalmente no hay necesidad de pasar la longitud de la cadena; se puede calcular con
strlen
si es necesario.Al pasar alrededor de un búfer , especialmente uno en el que está escrito, entonces absolutamente debe pasar el tamaño del búfer. Para un búfer de destino, esto permite a la persona que llama asegurarse de que no desborde el búfer. Para un búfer de entrada, permite que la persona que llama evite leer más allá del final, especialmente si el búfer de entrada contiene datos arbitrarios procedentes de una fuente no confiable.
Quizás haya cierta confusión porque las cadenas y los buffers podrían serlo
char*
y porque muchas funciones de cadena generan nuevas cadenas al escribir en los buffers de destino. Algunas personas concluyen que las funciones de cadena deben tomar longitudes de cadena. Sin embargo, esta es una conclusión inexacta. La práctica de incluir un tamaño con un búfer (ya sea que ese búfer se use para cadenas, matrices de enteros, estructuras, lo que sea) es un mantra más útil y más general.(En el caso de la lectura de una cadena a partir de una fuente no confiable (por ejemplo, un conector de red), es importante para suministrar una longitud ya que no podría ser terminado NUL-la entrada. Sin embargo , usted debe no tenemos en cuenta la entrada a ser una cadena. Usted debe tratarlo como un búfer de datos arbitrario que podría contener una cadena (pero no lo sabrá hasta que realmente lo valide), por lo que esto sigue el principio de que los búferes deben tener tamaños asociados y que las cadenas no los necesitan).
fuente
Si las funciones se usan principalmente con literales de cadena, el dolor de tratar con longitudes explícitas se puede minimizar definiendo algunas macros. Por ejemplo, dada una función API:
uno podría definir una macro:
y luego invocarlo como se muestra en:
Si bien es posible llegar a cosas "creativas" para pasar esa macro que se compilará pero que en realidad no funcionará, el uso de
""
cualquier lado de la cadena dentro de la evaluación de "sizeof" debería detectar intentos accidentales de usar caracteres punteros que no sean literales de cadena descompuestos [en ausencia de ellos""
, un intento de pasar un puntero de caracteres daría erróneamente la longitud como el tamaño de un puntero, menos uno.Un enfoque alternativo en C99 sería definir un tipo de estructura de "puntero y longitud" y definir una macro que convierta un literal de cadena en un literal compuesto de ese tipo de estructura. Por ejemplo:
Tenga en cuenta que si uno usa dicho enfoque, debe pasar dichas estructuras por valor en lugar de pasar sus direcciones. De lo contrario, algo como:
puede fallar ya que la vida útil de los literales compuestos terminaría en los extremos de sus declaraciones adjuntas.
fuente