Funciones que devuelven cadenas, ¿buen estilo?

11

En mis programas en C, a menudo necesito una forma de hacer una representación en cadena de mis ADT. Incluso si no necesito imprimir la cadena en la pantalla de ninguna manera, es bueno tener ese método para la depuración. Entonces, este tipo de función a menudo surge.

char * mytype_to_string( const mytype_t *t );

Realmente me doy cuenta de que tengo (al menos) tres opciones aquí para manejar la memoria para que la cadena regrese.

Alternativa 1: Almacenar la cadena de retorno en una matriz de caracteres estáticos en la función. No necesito pensar mucho, excepto que la cadena se sobrescribe en cada llamada. Lo cual puede ser un problema en algunas ocasiones.

Alternativa 2: Asignar la cadena en el montón con malloc dentro de la función. Realmente ordenado ya que entonces no necesitaré pensar en el tamaño de un búfer o la sobrescritura. Sin embargo, tengo que recordar liberar () la cadena cuando termine, y luego también necesito asignar una variable temporal para que pueda liberar. y luego la asignación del montón es realmente mucho más lenta que la asignación de la pila, por lo tanto, sea un cuello de botella si esto se repite en un bucle.

Alternativa 3: Pase el puntero a un búfer y deje que la persona que llama asigne ese búfer. Me gusta:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Esto trae más esfuerzo a la persona que llama. También noto que esta alternativa me da otra opción en el orden de los argumentos. ¿Qué argumento debería tener primero y último? (en realidad seis posibilidades)

Entonces, ¿cuál debería preferir? ¿Nada porque? ¿Existe algún tipo de estándar no escrito entre los desarrolladores de C?

Øystein Schønning-Johansen
fuente
3
Solo una nota de observación, la mayoría de los sistemas operativos utilizan la opción 3: de todas formas, la persona que llama asigna el búfer; le dice al puntero del búfer y la capacidad; la persona que llama llena el búfer y también devuelve la longitud real de la cadena si el búfer es insuficiente. Ejemplo: sysctlbynameen OS X e iOS
rwong

Respuestas:

11

Los métodos que más he visto son 2 y 3.

El buffer provisto por el usuario es realmente bastante simple de usar:

char[128] buffer;
mytype_to_string(mt, buffer, 128);

Aunque la mayoría de las implementaciones devolverán la cantidad de búfer utilizado.

La opción 2 será más lenta y peligrosa cuando se usan bibliotecas vinculadas dinámicamente donde pueden usar diferentes tiempos de ejecución (y diferentes montones). Por lo tanto, no puede liberar lo que ha sido mal colocado en otra biblioteca. Esto entonces requiere una free_string(char*)función para tratarlo.

monstruo de trinquete
fuente
¡Gracias! Creo que también me gusta la Alternativa 3. Sin embargo, quiero poder hacer cosas como: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));y, por lo tanto, no me gustaría devolver la longitud utilizada, sino el puntero a la cadena. El comentario dinámico de la biblioteca es realmente importante.
Øystein Schønning-Johansen
¿No debería ser esto sizeof(buffer) - 1para satisfacer al \0terminador?
Michael-O
1
@ Michael-O no, el término nulo se incluye en el tamaño del búfer, lo que significa que la cadena máxima que se puede colocar es 1 menos que el tamaño pasado. Este es el patrón que la cadena segura funciona en la biblioteca estándar como el snprintfuso.
monstruo de trinquete
@ratchetfreak Gracias por la aclaración. Sería bueno extender la respuesta con esa sabiduría.
Michael-O
0

Idea de diseño adicional para el n. ° 3

Cuando sea posible, también proporcione el tamaño máximo necesario para mytypeel mismo archivo .h que mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Ahora el usuario puede codificar en consecuencia.

char buf[MYTYPE_TO_STRING_SIZE];
puts(mytype_to_string(mt, buf, sizeof buf));

Orden

El tamaño de las matrices, cuando primero, permite los tipos de VLA.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

No es tan importante con una sola dimensión, pero es útil con 2 o más.

void matrix(size_t row, size_t col, double matrix[row][col]);

Recuerdo que leer tener el tamaño primero es un idioma preferido en la próxima C. Necesito encontrar esa referencia ...

chux - Restablece a Monica
fuente
0

Como una adición a la excelente respuesta de @ ratchetfreak, señalaría que la alternativa # 3 sigue un paradigma / patrón similar al de las funciones estándar de la biblioteca C.

Por ejemplo, strncpy.

char * strncpy ( char * destination, const char * source, size_t num );

Seguir el mismo paradigma ayudaría a reducir la carga cognitiva para los nuevos desarrolladores (o incluso su futuro yo) cuando necesiten usar su función.

La única diferencia con lo que tiene en su publicación sería que el destinationargumento en las bibliotecas C tiende a aparecer primero en la lista de argumentos. Entonces:

char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen ); 
John Go-Soco
fuente
-2

Además del hecho de que lo que está proponiendo hacer es un mal olor a código, la alternativa 3 me suena mejor. También pienso como @ gnasher729 que estás usando el idioma incorrecto.

John Pfersich
fuente
¿Qué consideras exactamente un código de olor? Por favor elabora.
Hulk
-3

Para ser honesto, es posible que desee cambiar a un idioma diferente donde devolver una cadena no sea una operación compleja, que requiera mucho trabajo y que sea propensa a errores.

Podría considerar C ++ u Objective-C donde podría dejar el 99% de su código sin cambios.

gnasher729
fuente