2009-08-17 12 views
6

Estoy escribiendo algunas funciones que manipulan cadenas en C y devuelven extractos de la cadena.Formato de función en un programa C

¿Cuáles son sus pensamientos sobre buenos estilos para devolver los valores de las funciones.

Refiriéndose a Steve McConnell's Code Complete (sección 5.8 en la edición de 1993), sugiere que utilizo el siguiente formato:

void my_function (char *p_in_string, char *p_out_string, int *status) 

Las alternativas que estoy considerando son:

devolver el resultado de la función (opción 2) usando:

char* my_function (char *p_in_string, int *status) 

devolver el estado de la función (opción 3) usando:

int my_function (char *p_in_string, char *p_out_string) 

En la opción 2 que sería volver la dirección de una variable local de mi_funcion pero mi función que llama estaría utilizando el valor de inmediato por lo que considero que esto sea OK y asume la posición de memoria no ha sido reutilizado (correcta yo en esto si estoy equivocado).

¿Esto se debe a un estilo y preferencia personal o debería considerar otros problemas?

+4

"En la opción 2 que sería volver la dirección de una variable local de mi_funcion pero mi función que llama estaría utilizando el valor de inmediato por lo que considero que esto sea OK y asume la posición de memoria no ha sido reutilizado (corrígeme si estoy equivocado) ". Esa no sería una buena suposición para hacer. – Amber

+1

La misma forma no es sensible para todas las operaciones de cadena. La forma que tome la llamada de función dependerá de lo que haga. Y no, utilizar el puntero a una variable local no es seguro, bajo ninguna circunstancia. –

+2

opción 2 es peligroso, no debe suponer un puntero a una función que la variable de pila local permanece después de la llamada. el único caso que sería algo seguro sería una función variable estática local, que es una mala idea por otras razones (seguridad y reentrada de hilos) – Hasturkun

Respuesta

6

La opción 3 es prácticamente el estándar tácito (?) De la industria. Si una función C basada en IO que devuelve un entero, devuelve un valor entero distinto de cero, casi siempre significa que la operación IO falló. Es posible que desee consultar this Wikibook's section en valores devueltos en C/C++.

La razón por la que las personas usan 0 para tener éxito es porque solo hay una condición de éxito. Entonces, si devuelve un valor distinto de cero, se busca de alguna manera qué significa el valor distinto de cero en términos de errores. Quizás un 1 significa que no pudo asignar memoria, 2 significa que el argumento no era válido, 3 significa que hubo algún tipo de error de IO, por ejemplo. Técnicamente, normalmente no devolvería 1, pero devolvería XXX_ERR_COULD_NOT_MALLOC o algo así.

También, never return addresses of local variables. A menos que lo haya publicado personalmente, no hay garantías sobre la dirección de esa variable una vez que regrese de la función. Lee el enlace para más información.

+0

Excepto para operaciones de cadena, que normalmente devuelven un carácter * y, por lo general, no pueden fallar. –

+0

Probablemente no llame a las funciones 'str *' basadas en IO. Pero, ¿alguna operación en los sockets de red, por ejemplo? Si devuelven un valor distinto de cero, cuidado. –

+0

Pero la pregunta ES acerca de las funciones de cadena. –

2

En la opción 2 que sería volver la dirección de una variable local de mi_funcion pero mi función que llama estaría utilizando el valor inmediatamente por lo que consideran que se trata Aceptar y asumir la posición de memoria tiene no ha sido reutilizado (corrígeme si estoy equivocado ).

Lo siento, pero estás equivocado, voy con el método de Steve McConnell, o el último método (por cierto en el primer método, "int status" debe ser "int * Estado".

Te perdonan por pensar que estarías en lo cierto, y podría funcionar durante las primeras 99,999 veces que ejecutas el programa, pero la vez número 100000 es el puntapié. En una arquitectura con múltiples subprocesos o incluso en múltiples procesos, puedes ' Confío en que alguien o algo no haya tomado ese segmento de memoria y lo haya usado antes de llegar a él.

Mejor prevenir que lamentar.

0

segunda Estilo:

No asuma que la memoria no se utilizará. Puede haber hilos que pueden devorar ese recuerdo y te queda nada más que basura sin fin.

1

La segunda opción es problemática porque tiene que obtener memoria para la cadena de resultados, por lo que puede usar un búfer estático (que posiblemente cause varios problemas) o asignar memoria, que a su vez puede causar fugas de memoria desde la llamada función tiene la responsabilidad de liberarlo después de su uso, algo que se olvida fácilmente.

También existe la opción 4,

char* my_function (char *p_in_string, char* p_out_string) 

cual simplemente devuelve p_out_string por conveniencia.

+0

He visto este estilo varias veces antes, supongo que es útil para que pueda incluir la llamada dentro de una operación condicional y probar su devolución dentro de la llamada condicional. – David

+0

David: este estilo le permite usar el resultado de la función como argumento para otra llamada a función, p. Ej. another_function (my_function (a, b), my_function (c, d), my_function (e, f)); en lugar de my_function (a, b); my_function (c, d); my_function (e, f); otra_función (b, d, f); –

0

Prefiero la opción 3. Esto es así que puedo hacer una comprobación de errores para la función en línea, es decir, en las declaraciones if. Además, me da el alcance para agregar un parámetro adicional para la longitud de la cadena, si fuera necesario.

int my_function(char *p_in_string, char **p_out_string, int *p_out_string_len) 
0

En cuanto a la opción 2:
Si devuelve un puntero a una variable local, que se ha asignado en la pila, el comportamiento es indefinido.
Si devuelve un puntero a una parte de la memoria que usted asignó (malloc, calloc, ...), esto sería seguro (pero feo, ya que podría olvidar free()).

Yo voto por la opción 3:
Le permite administrar la memoria fuera de my_function(...) y también puede devolver algún código de estado.

1

una manera más segura sería:

int my_function(const char* p_in_string, char* p_out_string, unsigned int max_out_length); 

la función devolvería el estado, por lo que es el registro de entrada capaces de inmediato al igual que en

if(my_function(....)) 

y la persona que llama asignar la memoria para la salida, porque

  1. la persona que llama tendrá que liberarlo y se realiza mejor en el mismo nivel
  2. la persona que llama sabrá cómo se maneja la asignación de memoria, en general, no la función
0

Yo diría que la opción 3 es la mejor manera de evitar problemas de gestión de memoria. También puede hacer una comprobación de errores usando el entero de estado.

0

También hay un punto a considerar si su función es de tiempo crítico. En la mayoría de las arquitecturas, es más rápido usar el valor de retorno que usar el puntero de referencia. Tuve el caso cuando uso el valor de retorno de la función. Pude evitar los accesos a la memoria en un bucle interno, pero usando el puntero del parámetro, el valor siempre se escribía en la memoria (el compilador no sabe si se accederá al valor a través de otro puntero en otro lugar). Con algún compilador, puede incluso aplicar atributos al valor de retorno, que no se pueden expresar en los punteros. Con una función como strlen, por ejemplo, algunos compiladores saben que entre llamadas de strlen, si el puntero no se modificó, se devolverá el mismo valor y así evitará recuperar la función. En Gnu-C puede dar el atributo puro o incluso const al valor de retorno (cuando corresponda), cosa que es imposible con un parámetro de referencia.

1
  1. void my_function (char *p_in_string, char *p_out_string, int *status)
  2. char* my_function (char *p_in_string, int *status)
  3. int my_function (char *p_in_string, char *p_out_string)

En todos los casos, la cadena de entrada debe ser constante, a menos mi_funcion explícitamente se le está dando permiso para escribir - por ejemplo - temporal cero de terminación de o marcadores en la cadena de entrada.

El segundo formulario solo es válido si my_function llama "malloc" o alguna variante para asignar el búfer. No es seguro en ninguna implementación c/C++ devolver los punteros a las variables locales/de ámbito de la pila. Por supuesto, cuando my_function llama a malloc en sí mismo, hay una cuestión de cómo se libera el búfer asignado.

En algunos casos, la persona que llama tiene la responsabilidad de liberar el búfer - llamando al free(), o, para permitir que diferentes capas utilicen diferentes asignadores, a través de un my_free_buffer(void*) que publique. Otro patrón frecuente es devolver un puntero a un buffer estático mantenido por my_function - con la condición de que la persona que llama no debe esperar que el buffer permanezca válido después de la próxima llamada a mi_función.

En todos los casos en que se pasa un puntero a un búfer de salida, debe emparejarse con el tamaño del búfer.

La forma que más prefiero es

int my_function(char const* pInput, char* pOutput,int cchOutput);

Esto devuelve 0 en caso de error, o el número de caracteres copiados en pOutput el éxito con cchOutput siendo el tamaño de pOutput para evitar my_function overruning el buffer pOutput. Si pOutput es NULL, devuelve el número de caracteres que pOutput necesita para ser exactamente. Incluyendo el espacio para un terminador nulo, por supuesto.

// This is one easy way to call my_function if you know the output is <1024 characters 
char szFixed[1024]; 
int cch1 = my_function(pInput,szFixed,sizeof(szFixed)/sizeof(char)); 

// Otherwise you can call it like this in two passes to find out how much to alloc 
int cch2 = my_function(pInput,NULL,0); 
char* pBuf = malloc(cch2); 
my_function(pInput,pBuf,cch2); 
Cuestiones relacionadas