C una función que devuelve una matriz

Si necesito escribir una función que devuelva una matriz: int *, ¿de qué manera es mejor?

  1. int* f(..data..)

o: void f(..data..,int** arr)

y llamamos f así: int* x; f(&x); int* x; f(&x); . (quizás ambos sean iguales pero no estoy seguro. pero si necesito devolver un ErrorCode (es un enumeración), entonces de la primera manera f obtendrá ErrorCode * y de la segunda manera, f devolverá un ErrorCode) .

Devolver una matriz es solo devolver una cantidad variable de datos.
Ese es un problema realmente antiguo, y los progtwigdores de C desarrollaron muchas respuestas para él:

  1. Quien llama pasa en búfer.
    1. El tamaño necesario se documenta y no se pasa, los búferes demasiado cortos son comportamiento indefinido : strcpy()
    2. El tamaño necesario se documenta y se pasa, los errores se señalan mediante el valor de retorno: strcpy_s()
    3. El tamaño del búfer se pasa por el puntero y la función llamada se reasigna con el asignador documentado según sea necesario: POSIX getline()
    4. El tamaño necesario es desconocido, pero se puede consultar llamando a la función con buffer-length 0: snprintf()
    5. El tamaño necesario es desconocido y no se puede consultar, se devuelve tanto como se ajusta en un búfer de tamaño aprobado. Si es necesario, se deben hacer llamadas adicionales para obtener el rest: fread()
    6. El tamaño necesario es desconocido, no se puede consultar, y pasar un búfer demasiado pequeño es un comportamiento indefinido . Este es un defecto de diseño, por lo tanto, la función está en desuso / eliminada en las versiones más nuevas, y se menciona aquí para completar: gets() .
  2. La persona que llama pasa una callback:
    1. La función de callback obtiene un parámetro de contexto: qsort_s()
    2. La función de callback no obtiene ningún parámetro de contexto. Obtener el contexto requiere magia: qsort()
  3. La persona que llama pasa un asignador: no se encuentra en la biblioteca estándar de C. Sin embargo, todos los contenedores de C ++ compatibles con el asignador admiten eso.
  4. El contrato de Callee especifica el desasignador. Llamar a uno equivocado es un comportamiento indefinido : fopen() -> fclose() strdup() -> free()
  5. El destinatario de la llamada devuelve un objeto que contiene el desasignador: Objetos COM
  6. La persona llamada utiliza un búfer compartido interno: asctime()

Tenga en cuenta que la matriz devuelta debe contener un objeto centinela u otro marcador, debe devolver la longitud por separado o debe devolver una estructura que contenga un puntero a los datos y la longitud.
Paso por referencia (puntero a tamaño o tal) ayuda allí.

En general, siempre que el usuario tenga que adivinar el tamaño o buscarlo en el manual, a veces se equivocará. Si no se equivoca, una revisión posterior podría invalidar su cuidadoso trabajo, por lo que no importa que una vez tuvo razón. De todos modos, de esta manera queda la locura (UB) .

Para el rest, elige el más cómodo y eficiente que puedas.

Respecto a un código de error: recuerda que hay un error.

Usualmente es más conveniente y semántico devolver la matriz.

 int* f(..data..) 

Si alguna vez necesita un manejo complejo de errores (por ejemplo, devolver valores de error), debe devolver el error como un int, y la matriz por valor.

No hay “mejor” aquí: usted decide qué enfoque se ajusta mejor a las necesidades de las personas que llaman.

Tenga en cuenta que ambas funciones están obligadas a otorgar a un usuario una matriz que asignan internamente, por lo que la desasignación de la matriz resultante se convierte en responsabilidad del llamante. En otras palabras, en algún lugar dentro de f() tendría un malloc , y el usuario que recibe los datos debe llamar a free() .

Tiene otra opción aquí: deje que la persona que llama le pase la matriz y devuelva un número que indique cuántos elementos ha vuelto a colocar en ella:

 size_t f(int *buffer, size_t max_length) 

Este enfoque le permite a la persona que llama pasarle un búfer en una memoria estática o automática, mejorando así la flexibilidad.

el modelo clásico es (asumiendo que también debe devolver el código de error)

 int f(...., int **arr) 

aunque no fluye tan bien como una función que devuelve la matriz

Tenga en cuenta que esta es la razón por la que el encantador lenguaje go admite múltiples valores de retorno.

También es uno de los motivos de las excepciones: elimina los indicadores de error de la función i / o space

La primera es mejor si no hay ningún requisito para tratar con un puntero ya existente en la función. El segundo se usa cuando ya tiene un puntero definido que apunta a un contenedor ya asignado (por ejemplo, una lista) y dentro de la función se puede cambiar el valor del puntero.

Si tiene que llamar a f como int* x; f(&x); int* x; f(&x); , no tienes mucha opción. Debe utilizar la segunda syntax, es decir, void f(..data..,int** arr) . Esto se debe a que no está utilizando el valor de retorno de todos modos en su código.

El enfoque depende de una tarea específica y quizás de su gusto personal o de una convención de encoding adoptada en su proyecto.

En general, me gustaría pasar los punteros como parámetros de “salida” en lugar de return una matriz por varias razones.

  1. Es probable que desee devolver una serie de elementos en la matriz junto con la propia matriz. Pero si haces esto:

     int f(const void* data, int** out_array); 

    Entonces, si ve la firma por primera vez, no puede saber qué devuelve la función, el número de elementos o un código de error, así que prefiero hacer esto:

     void f(const void* data, int** out_array, int* out_array_nelements); 

    O mejor:

     void f(const void* data, int** out_array, size_t* out_array_nelements); 

    La firma de la función debe ser autoexplicativa, y los nombres de los parámetros ayudan a lograrlo.

  2. La matriz de salida debe almacenarse en algún lugar. Necesitas asignar algo de memoria para la matriz. Si return un puntero a la matriz sin pasar el mismo puntero que el argumento, no puede asignar memoria en la stack. Quiero decir, no puedes hacer esto

     int f (const void *data) { int array[10]; return array; /* the array is likely deallocated when the function exits */ } 

    En su lugar, tiene que hacer una static int array[10] (que no es segura para subprocesos) o int *array = malloc(...) que conduce a pérdidas de memoria.

    Así que le sugiero que pase un puntero a la matriz que ya está asignada antes de la llamada a la función, como esto:

     void f(const void *data, int* out_array, size_t* out_nelements, size_t max_nelements); 

    El beneficio es que usted es libre de elegir dónde asignar la matriz:

    En la stack:

     int array[10] = { 0 }; size_t max_nelements = sizeof(array)/sizeof(array[0]); size_t nelements = 0; f(data, array, &nelements, max_nelements); 

    O en el montón:

     size_t nelements = 0; size_t max_nelements = 10; int *array = malloc(max_nelements * sizeof(int)); f(data, array, &nelements, max_nelements); 

    Ver, con este enfoque, usted es libre de elegir cómo asignar la memoria.