Devolver una matriz usando C

152

Soy relativamente nuevo en C y necesito ayuda con los métodos que tratan con matrices. Viniendo de la programación Java, estoy acostumbrado a poder decir int [] method()para devolver una matriz. Sin embargo, descubrí que con C tienes que usar punteros para las matrices cuando las devuelves. Siendo un nuevo programador, realmente no entiendo esto en absoluto, incluso con los muchos foros que he revisado.

Básicamente, estoy tratando de escribir un método que devuelva una matriz de caracteres en C. Proporcionaré el método (llamémoslo returnArray) con una matriz. Creará una nueva matriz a partir de la matriz anterior y le devolverá un puntero. Solo necesito ayuda sobre cómo comenzar esto y cómo leer el puntero una vez que se envía fuera de la matriz. Cualquier ayuda para explicar esto es apreciada.

Formato de código propuesto para la función de devolución de matriz

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

Llamador de la función

int main(){
 int i=0;
 char array []={1,0,0,0,0,1,1};
 char arrayCount=0;
 char* returnedArray = returnArray(&arrayCount); ///is this correct?
 for (i=0; i<10;i++)
  printf(%d, ",", returnedArray[i]);  //is this correctly formatted?
}

Todavía no lo he probado, ya que mi compilador de C no funciona en este momento, pero me gustaría resolverlo

usuario1506919
fuente
¿Es la matriz de retorno un tamaño conocido como se indica en su ejemplo de código? El único otro problema que veo además de los problemas de pila mencionados en las respuestas es que si su matriz de retorno es de un tamaño indeterminado, dada la forma en que funcionan los punteros / matrices en C, no sabrá qué tan grande es.
strangefreeworld
Sí, sé el tamaño de la matriz entrante en todo momento. El tamaño de la matriz de entrada y salida no cambiará.
user1506919
1
El desarrollo del lenguaje C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Respuestas:

225

No puede devolver matrices de funciones en C. Tampoco puede (no debería) hacer esto:

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

returned se crea con una duración de almacenamiento automática y las referencias a él serán inválidas una vez que deje su alcance de declaración, es decir, cuando regrese la función.

Deberá asignar dinámicamente la memoria dentro de la función o llenar un búfer preasignado proporcionado por la persona que llama.

Opción 1:

Asignar dinámicamente la memoria dentro de la función (persona que llama responsable de desasignar ret)

char *foo(int count) {
    char *ret = malloc(count);
    if(!ret)
        return NULL;

    for(int i = 0; i < count; ++i) 
        ret[i] = i;

    return ret;
}

Llámalo así:

int main() {
    char *p = foo(10);
    if(p) {
        // do stuff with p
        free(p);
    }

    return 0;
}

Opcion 2:

llenar un búfer preasignado proporcionado por la persona que llama (la persona que llama asigna bufy pasa a la función)

void foo(char *buf, int count) {
    for(int i = 0; i < count; ++i)
        buf[i] = i;
}

Y llámalo así:

int main() {
    char arr[10] = {0};
    foo(arr, 10);
    // No need to deallocate because we allocated 
    // arr with automatic storage duration.
    // If we had dynamically allocated it
    // (i.e. malloc or some variant) then we 
    // would need to call free(arr)
}
Ed S.
fuente
33
Opción 3: (una matriz estática)
moooeeeep
55
@moooeeeep: Sí, lo dejé a propósito para simplificar las cosas, pero sí, puede devolver un puntero a los datos estáticos declarados desde la función.
Ed S.
3
@ user1506919: Preferiría la opción 2, ya que está claro quién asigna y desasigna la memoria, pero agregaré un ejemplo para usted.
Ed S.
77
Opción 4: Devuelve una estructura que contiene una matriz de tamaño fijo.
Todd Lehman
2
Opción 5: Devuelve una unión que contiene una matriz de tamaño fijo.
sqr163
27

El tratamiento de C de las matrices es muy diferente al de Java, y tendrá que ajustar su pensamiento en consecuencia. Las matrices en C no son objetos de primera clase (es decir, una expresión de matriz no retiene su "matriz" en la mayoría de los contextos). En C, una expresión de tipo "matriz de elementos N de T" se convertirá implícitamente ("decaimiento") en una expresión de tipo "puntero a T", excepto cuando la expresión de matriz sea un operando de los operadores sizeofunarios &, o si el La expresión de matriz es un literal de cadena que se utiliza para inicializar otra matriz en una declaración.

Entre otras cosas, esto significa que no puede pasar una expresión de matriz a una función y recibirla como un tipo de matriz ; la función realmente recibe un tipo de puntero:

void foo(char *a, size_t asize)
{
  // do something with a
}

int bar(void)
{
  char str[6] = "Hello";
  foo(str, sizeof str);
}

En la llamada a foo, la expresión strse convierte de tipo char [6]a char *, razón por la cual foose declara el primer parámetro de en char *alugar de char a[6]. En sizeof str, dado que la expresión de matriz es un operando del sizeofoperador, no se convierte en un tipo de puntero, por lo que obtiene el número de bytes en la matriz (6).

Si está realmente interesado, puede leer El desarrollo del lenguaje C de Dennis Ritchie para comprender de dónde proviene este tratamiento.

El resultado es que las funciones no pueden devolver tipos de matriz, lo cual está bien ya que las expresiones de matriz tampoco pueden ser el objetivo de una asignación.

El método más seguro es que la persona que llama defina la matriz y pase su dirección y tamaño a la función que se supone que debe escribirle:

void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)
{
  ...
  dstArray[i] = some_value_derived_from(srcArray[i]);
  ...
}

int main(void)
{
  char src[] = "This is a test";
  char dst[sizeof src];
  ...
  returnArray(src, sizeof src, dst, sizeof dst);
  ...
}

Otro método es que la función asigne la matriz dinámicamente y devuelva el puntero y el tamaño:

char *returnArray(const char *srcArray, size_t srcSize, size_t *dstSize)
{
  char *dstArray = malloc(srcSize);
  if (dstArray)
  {
    *dstSize = srcSize;
    ...
  }
  return dstArray;
}

int main(void)
{
  char src[] = "This is a test";
  char *dst;
  size_t dstSize;

  dst = returnArray(src, sizeof src, &dstSize);
  ...
  free(dst);
  ...
}

En este caso, la persona que llama es responsable de desasignar la matriz con la freefunción de biblioteca.

Tenga dsten cuenta que en el código anterior hay un puntero simple a char, no un puntero a una matriz de char. La semántica de puntero y matriz de C es tal que puede aplicar el operador de subíndice []a una expresión de tipo de matriz o tipo de puntero; ambos src[i]y dst[i]accederá al ielemento 'th de la matriz (aunque solo srctenga un tipo de matriz).

Usted puede declarar un puntero a una matriz de n elementos de Ty hacer algo similar:

char (*returnArray(const char *srcArr, size_t srcSize))[SOME_SIZE]
{
  char (*dstArr)[SOME_SIZE] = malloc(sizeof *dstArr);
  if (dstArr)
  {
    ...
    (*dstArr)[i] = ...;
    ...
  }
  return dstArr;
}

int main(void)
{
  char src[] = "This is a test";
  char (*dst)[SOME_SIZE];
  ...
  dst = returnArray(src, sizeof src);
  ...
  printf("%c", (*dst)[j]);
  ...
}

Varios inconvenientes con lo anterior. En primer lugar, las versiones anteriores de C esperan SOME_SIZEser una constante de tiempo de compilación, lo que significa que la función solo funcionará con un tamaño de matriz. En segundo lugar, debe desreferenciar el puntero antes de aplicar el subíndice, que satura el código. Los punteros a matrices funcionan mejor cuando se trata de matrices multidimensionales.

John Bode
fuente
2
Su enlace a "el desarrollo de C" se ha roto ... parece que debería dirigirnos aquí: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@Kundor: Lo que barrecibe es un puntero, no una matriz. En el contexto de una declaración de parámetro de función, T a[N]y T a[]ambos se tratan como T *a.
John Bode
@ JohnBode: ¡Tienes razón! Por alguna razón, pensé que se pasaron matrices de tamaño fijo en la pila. Recuerdo una ocasión, hace muchos años, cuando descubrí que el tamaño de una matriz tenía que especificarse en la firma del parámetro, pero debo haber estado confundido.
Nick Matteo
@JohnBode, en la segunda línea de la primera parte del código: el void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)último parámetro debe estar en el size_ttipo no char.
Seyfi
11

No estoy diciendo que esta sea la mejor solución o una solución preferida para el problema dado. Sin embargo, puede ser útil recordar que las funciones pueden devolver estructuras. Aunque las funciones no pueden devolver matrices, las matrices pueden envolverse en estructuras y la función puede devolver la estructura llevando así la matriz consigo. Esto funciona para matrices de longitud fija.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    typedef
    struct 
    {
        char v[10];
    } CHAR_ARRAY;



    CHAR_ARRAY returnArray(CHAR_ARRAY array_in, int size)
    {
        CHAR_ARRAY returned;

        /*
        . . . methods to pull values from array, interpret them, and then create new array
        */

        for (int i = 0;  i < size; i++ )
            returned.v[i] = array_in.v[i] + 1;

        return returned; // Works!
    } 




    int main(int argc, char * argv[])
    {
        CHAR_ARRAY array = {1,0,0,0,0,1,1};

        char arrayCount = 7;

        CHAR_ARRAY returnedArray = returnArray(array, arrayCount); 

        for (int i = 0; i < arrayCount; i++)
            printf("%d, ", returnedArray.v[i]);  //is this correctly formatted?

        getchar();
        return 0;
    }

Invito comentarios sobre las fortalezas y debilidades de esta técnica. No me he molestado en hacerlo.

Indinfer
fuente
1
No está claro por qué esta no es la respuesta aceptada. La pregunta no era si es posible devolver un puntero a una matriz.
Frank Puck
¿Está asignada la memoria CHAR_ARRAY returneden el montón? Ciertamente no puede en la pila (en el marco de la pila de la returnArray()derecha?
Minh Tran
9

¿Qué tal esta implementación deliciosamente malvada?

array.h

#define IMPORT_ARRAY(TYPE)    \
    \
struct TYPE##Array {    \
    TYPE* contents;    \
    size_t size;    \
};    \
    \
struct TYPE##Array new_##TYPE##Array() {    \
    struct TYPE##Array a;    \
    a.contents = NULL;    \
    a.size = 0;    \
    return a;    \
}    \
    \
void array_add(struct TYPE##Array* o, TYPE value) {    \
    TYPE* a = malloc((o->size + 1) * sizeof(TYPE));    \
    TYPE i;    \
    for(i = 0; i < o->size; ++i) {    \
        a[i] = o->contents[i];    \
    }    \
    ++(o->size);    \
    a[o->size - 1] = value;    \
    free(o->contents);    \
    o->contents = a;    \
}    \
void array_destroy(struct TYPE##Array* o) {    \
    free(o->contents);    \
}    \
TYPE* array_begin(struct TYPE##Array* o) {    \
    return o->contents;    \
}    \
TYPE* array_end(struct TYPE##Array* o) {    \
    return o->contents + o->size;    \
}

C Principal

#include <stdlib.h>
#include "array.h"

IMPORT_ARRAY(int);

struct intArray return_an_array() {
    struct intArray a;
    a = new_intArray();
    array_add(&a, 1);
    array_add(&a, 2);
    array_add(&a, 3);
    return a;
}

int main() {
    struct intArray a;
    int* it;
    int* begin;
    int* end;
    a = return_an_array();
    begin = array_begin(&a);
    end = array_end(&a);
    for(it = begin; it != end; ++it) {
        printf("%d ", *it);
    }
    array_destroy(&a);
    getchar();
    return 0;
}
pyrospade
fuente
2
Esto es endiabladamente delicioso como para despertar mi curiosidad. ¿Puedes explicar un poco más lo que hiciste allí o tal vez sugerir una lectura a esta delicia que llamas? Gracias por adelantado.
Unheilig
1
@Unheilig: tenga en cuenta que hay errores potenciales de sime en esto, fue solo una prueba de Concept. Dicho esto, el truco es devolver a structcomo un contenedor / objeto de matriz. Piense en ello como un C ++ std :: vector. El preprocesador expandiría la intversión de esto a struct intArray { int* contents; int size; };.
pyrospade
1
Me gusta el acercamiento. pro: esta es una solución genérica; contra: solución intensiva en memoria. No es óptimo para vectores de tamaños conocidos. De todos modos, esto se puede actualizar con la asignación de tamaño inicial. Definitivamente agregaría alguna verificación de asignación. Muy buena propuesta para comenzar :)
urkon
Objeto orientado-esk preposesing mix-mash. Me gusta.
Jack Giffin
6

En su caso, está creando una matriz en la pila y una vez que abandona el alcance de la función, la matriz se desasignará. En su lugar, cree una matriz asignada dinámicamente y devuélvale un puntero.

char * returnArray(char *arr, int size) {
    char *new_arr = malloc(sizeof(char) * size);
    for(int i = 0; i < size; ++i) {
        new_arr[i] = arr[i];
    }
    return new_arr;
}

int main() {

    char arr[7]= {1,0,0,0,0,1,1};
    char *new_arr = returnArray(arr, 7);

    // don't forget to free the memory after you're done with the array
    free(new_arr);

}
Hombre de una manera
fuente
2
No hay newoperador en C. Eso es C ++.
Eric Postpischil
1
Y sizeof(char)está garantizado que lo será 1, por lo que en este caso puede soltar ese bit malloc.
Ed S.
ok, así que si quisiera imprimir el contenido de la nueva matriz, ¿podría simplemente hacer mi declaración 'printf' pero reemplazar 'returnArray' con 'arr'?
user1506919
No está llamando a la función correctamente (solo un argumento cuando la firma requiere dos).
Ed S.
Estás pasando en &arr. Quieres arrser un char *y pasarlo usando arr.
Chris
4

Puede hacerlo utilizando la memoria de almacenamiento dinámico (a través de la invocación malloc () ) como otras respuestas informadas aquí, pero siempre debe administrar la memoria (use la función free () cada vez que llame a su función). También puedes hacerlo con una matriz estática:

char* returnArrayPointer() 
{
static char array[SIZE];

// do something in your array here

return array; 
}

Puede usarlo sin preocuparse por la administración de la memoria.

int main() 
{
char* myArray = returnArrayPointer();
/* use your array here */
/* don't worry to free memory here */
}

En este ejemplo, debe usar una palabra clave estática en la definición de matriz para establecer en toda la aplicación la vida útil de la matriz, por lo que no se destruirá después de la declaración de devolución. Por supuesto, de esta manera usted ocupa TAMAÑO de bytes en su memoria durante toda la vida de la aplicación, ¡así que dimensione correctamente!

mengo
fuente
2

Su método devolverá una variable de pila local que fallará gravemente. Para devolver una matriz, cree una fuera de la función, pásela por dirección en la función, luego modifíquela o cree una matriz en el montón y devuelva esa variable. Ambos funcionarán, pero el primero no requiere ninguna asignación de memoria dinámica para que funcione correctamente.

void returnArray(int size, char *retArray)
{
  // work directly with retArray or memcpy into it from elsewhere like
  // memcpy(retArray, localArray, size); 
}

#define ARRAY_SIZE 20

int main(void)
{
  char foo[ARRAY_SIZE];
  returnArray(ARRAY_SIZE, foo);
}
Michael Dorgan
fuente
0

Puedes usar un código como este:

char *MyFunction(some arguments...)
{
    char *pointer = malloc(size for the new array);
    if (!pointer)
        An error occurred, abort or do something about the error.
    return pointer; // Return address of memory to the caller.
}

Cuando haga esto, la memoria debería liberarse más tarde, pasando la dirección a libre.

Hay otras opciones Una rutina puede devolver un puntero a una matriz (o parte de una matriz) que es parte de alguna estructura existente. La persona que llama puede pasar una matriz, y la rutina simplemente escribe en la matriz, en lugar de asignar espacio para una nueva matriz.

Eric Postpischil
fuente