2011-12-22 20 views
14

Tengo una función que tiene dos enteros y devuelve una cadena. En este momento tengo esto:manera correcta devolver una cadena desde una función

char* myfunc(int a, int b, int* len) 
{ 
    int retLen = ... 
    char* ret = malloc(retLen + 1); 

    if (len != NULL) 
    { 
     *len = retLen; 
    } 

    return ret; 
} 

Sin embargo, la mayoría de las funciones de la biblioteca C tienden a hacer algo más como:

int myfunc(char* ret, int a, int b) 
{ 
    ... 

    return retLen; 
} 

Se espera entonces para asignar la memoria para la función de llenar. Esto le permite hacer un poco más como elegir dónde se asigna la cadena.

En este caso, aunque se requieren algunos cálculos en la función para obtener la longitud, y no hay razón para tener un búfer de cualquier tamaño que no sea el necesario. No hay un límite superior en el tamaño del búfer (no uno que sea razonable de todos modos).

¿Qué se considera una buena práctica al devolver una cadena cuya longitud se encuentra dinámicamente dadas las entradas?

Respuesta

7

Un patrón que vi en los programas de modo de núcleo es:

  1. se llama a la función una vez, con un poco de memoria asignada si le sucede que tiene alguna disponible, o null como parámetro si resulta que no tienen ninguno
  2. Si tenía memoria asignada y la función la encontró lo suficiente, coloca el resultado en esa memoria y devuelve OK
  3. Si no tenía memoria para pasar, o la memoria pasaba demasiado poco, la función devuelve ERROR_NOT_ENOUGH_MEMORY, y pone en un parámetro de salida la memoria necesaria.
    • A continuación, asignar esta memoria necesaria y llamar de nuevo la función

muestra:

int myfunc(
    __out char* output, 
    __in size_t given, 
    __out size_t needed_or_resulted, 
    extra params ... 
){ 
    ... implementation 
} 

El needed_or_resulted también se puede utilizar para transmitir la cantidad de la memoria dada se utilizó en caso de éxito

Para ser utilizado como:

int result = myfunc(output, given, needed_or_resulted, extra params ...); 
if(result == OK) { 
    // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
} else if(result == ERROR_NOT_ENOUGH_MEMORY) { 
    output = malloc(needed ... 
    result = myfunc(output, given, needed_or_resulted, extra params ...); 
    if(result == OK) { 
     // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
    } else if(result == ERROR_OTHER) { 
     // handle other possible errors 
    } else { 
     // handle unknown error 
    } 
} else if(result == ERROR_OTHER) { 
    // handle other possible errors 
} else { 
    // handle unknown error 
} 
+0

Por supuesto, con este patrón, pasaría la longitud junto con el puntero. Incluso podría ser un parámetro de entrada/salida (pasado como 'size_t *'). – cHao

+0

Sí, de hecho, edité la firma. – clyfe

+0

Muy buena respuesta que ayuda mucho, así que gracias. Sin embargo, un par de preguntas: ¿por qué se prefiere a una función como 'size_t myfunc (char * output, size_t given, ...)' que efectivamente devuelve 'needed_or_resulted' para que el usuario la compare y obtenga su propio significado para el éxito? Además, ¿por qué se prefiere esto a una segunda función como 'myfuncLength' que hace los cálculos de longitud necesarios? – Matt

2

Tiene razón acerca del motivo por el que se prefiere la firma int myfunc(char* ret, int a, int b). En realidad, explica otra cosa: por qué es necesario devolver la longitud (el tamaño del búfer es el MAX, por lo que generalmente debemos informarle a la persona que llama qué tanto usamos realmente).

Cuando asigna cadenas dentro de una función, generalmente no devuelve el tamaño de la cadena, porque strlen se puede utilizar para averiguarlo. Consulte strdup para ver un ejemplo de una función que asigna cadenas dinámicamente. Así que me gustaría cambiar la firma de su función para

char* myfunc(int a, int b) { 
    ... 
} 
+0

si estoy leyendo la pregunta correcta, no hay 'MAX'. O si hay uno, es demasiado grande para crear búferes. – cHao

+0

@cHao Correcto, este es el 'MÁXIMO 'imaginario que el OP dice que no quiere introducir. En la primera parte de la respuesta, explico por qué tenemos que devolver la longitud cuando hay un 'MÁX', sin sugerir que el OP debería tener uno. La segunda parte se ocupa más de la situación en cuestión. – dasblinkenlight

+0

Además, esto hace que la regla sobre 'liberar' lo que '' malloc' tiene en la oreja. Será mejor que tenga una idea clara sobre a quién pertenece la memoria en la documentación para esta función. – cHao

3

Este último es mejor, ya que la persona que llama en pistas acerca de quién es responsable de liberar la memoria. El primero causa grandes problemas si el llamante y el destinatario utilizan diferentes implementaciones de malloc (por ejemplo, en Windows, la depuración y el lanzamiento suelen utilizar modelos de memoria incompatibles).

1

Me gustaría pasar una char * por referencia.Si la función se ejecuta con éxito, asigne la cadena y asígnela a la referencia del puntero y devuelva la longitud de la cadena. Si se produce un error, establezca errno y devuelva -1.

int myfunc(int a, int b, char ** str) 
{ 
    int retLen; 

    /* code to calculate string length required */ 

    if (!(str)) 
    { 
     errno = EINVAL; 
     return(-1); 
    }; 
    if (!(*str = malloc(retLen))) 
     return(-1); 

    /* calculate new value and store to string */ 

    return(retLen); 
} 
2

Siga la interfaz de snprintf, una función estándar con exactamente el mismo problema:

size_t myfunc(char *s, size_t n, int a, int b); 

bytes de salida más allá de la n-primero se descartarán en lugar de ser por escrito a la matriz, y un byte nulo se escribe al final de bytes escritos realmente en la matriz.

Al finalizar con éxito, la función snprintf() devolverá el número de bytes que se escribirían en s si n hubiera sido suficientemente grande, excluyendo el byte nulo> de terminación.

Si el valor de n es cero en una llamada a snprintf(), nada se escrito, el número de bytes que se han escrito había n sido suficientemente grande excluyendo el terminador nulo, se devolverá, y s puede ser un puntero nulo.

uso típico:

size_t needed = myfunc(0, 0, a, b) + 1; 
char *buf = malloc(needed); 
if (buf) { 
    myfunc(buf, needed, a, b); 
} 

Usted podría incluir el byte nulo en el número devuelto - que hace que el código de llamada más simple, aunque un poco menos familiar para personas acostumbradas a snprintf estándar.

Si computar retLen es asombrosamente costoso, podría haber un argumento para una función que lo compute, ya que genera la cadena y devuelve un búfer asignado del tamaño correcto (tal vez teniendo realloc ed en el camino). Pero normalmente ni siquiera lo pensaría. Para comodidad de los usuarios que desean asignar, simplemente coloque el código anterior en una función myfunc_alloc y no importa que duplique un poco el trabajo. Los usuarios que ya tienen un buffer pueden llamar directamente myfunc así:

if (myfunc(buf, bufsize, a, b) >= bufsize) { 
    printf("buffer too small, string (%s) has been truncated\n", buf); 
} 
Cuestiones relacionadas