Restituzione di un array mediante C


153

Sono relativamente nuovo in C e ho bisogno di aiuto con i metodi di gestione degli array. Venendo dalla programmazione Java, sono abituato a poter dire int [] method()per restituire un array. Tuttavia, ho scoperto che con C devi usare i puntatori per le matrici quando le restituisci. Essendo un nuovo programmatore, non lo capisco affatto, anche con i molti forum che ho visitato.

Fondamentalmente, sto cercando di scrivere un metodo che restituisce un array di caratteri in C. Fornirò il metodo (chiamiamolo returnArray) con un array. Creerà un nuovo array dall'array precedente e vi restituirà un puntatore. Ho solo bisogno di aiuto su come iniziare e come leggere il puntatore una volta inviato dall'array. Qualsiasi aiuto per spiegarlo è apprezzato.

Formato di codice proposto per la funzione di restituzione di array

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?
} 

Chiamante della funzione

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?
}

Non l'ho ancora testato in quanto il mio compilatore C non funziona al momento, ma vorrei capirlo


L'array di ritorno ha una dimensione nota come indicato nell'esempio di codice? L'unico altro gotcha che vedo oltre ai problemi di stack menzionati nelle risposte è che se la matrice di ritorno ha una dimensione indeterminata, dato il modo in cui i puntatori / array funzionano in C, non saprai quanto è grande.
strangefreeworld,

Sì, conosco sempre le dimensioni dell'array in entrata. La dimensione dell'array di input e output non cambierà.
user1506919

1
Lo sviluppo del linguaggio C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Risposte:


225

Non è possibile restituire matrici dalle funzioni in C. Inoltre, non è possibile (non è necessario) eseguire ciò:

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 viene creato con durata di memorizzazione automatica e i riferimenti ad essa non saranno più validi una volta che esce dal suo ambito di dichiarazione, ovvero quando la funzione ritorna.

Sarà necessario allocare dinamicamente la memoria all'interno della funzione o riempire un buffer preallocato fornito dal chiamante.

Opzione 1:

alloca dinamicamente la memoria all'interno della funzione (chiamante responsabile della deallocazione 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;
}

Chiamalo così:

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

    return 0;
}

Opzione 2:

riempire un buffer preallocato fornito dal chiamante (il chiamante alloca bufe passa alla funzione)

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

E chiamalo così:

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)
}

33
Opzione 3: (un array statico)
moooeeeep

5
@moooeeeep: Sì, l'ho lasciato di proposito per mantenere le cose semplici, ma sì, puoi restituire un puntatore ai dati statici dichiarati dall'interno della funzione.
Ed S.

3
@ user1506919: Preferirei in realtà l'opzione 2 in quanto è chiaro chi alloca e dealloca la memoria, ma aggiungerò un esempio per te.
Ed S.

7
Opzione 4: restituisce uno struct che contiene un array di dimensioni fisse.
Todd Lehman,

2
Opzione 5: restituisce un'unione che contiene un array di dimensioni fisse.
sqr163,

27

Il trattamento delle matrici di C è molto diverso da quello di Java e dovrai adattare il tuo pensiero di conseguenza. Le matrici in C non sono oggetti di prima classe (ovvero, un'espressione di matrice non mantiene la sua "matrice" nella maggior parte dei contesti). In C, un'espressione di tipo "Matrice di elementi N di T" sarà convertita in modo implicito ("decadimento") in un'espressione di tipo "puntatore a T", tranne quando l'espressione di matrice è un operando degli operatori sizeofunari &o, o se il espressione di matrice è una stringa letterale utilizzata per inizializzare un altro array in una dichiarazione.

Tra le altre cose, ciò significa che non è possibile passare un'espressione di matrice a una funzione e averla ricevuta come tipo di matrice ; la funzione riceve effettivamente un tipo di puntatore:

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

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

Nella chiamata a foo, l'espressione strviene convertita dal tipo char [6]in char *, motivo per cui fooviene dichiarato il primo parametro di char *aanziché char a[6]. In sizeof str, poiché l'espressione di matrice è un operando sizeofdell'operatore, non viene convertita in un tipo di puntatore, quindi ottieni il numero di byte nella matrice (6).

Se sei davvero interessato, puoi leggere Lo sviluppo del linguaggio C di Dennis Ritchie per capire da dove proviene questo trattamento.

Il risultato è che le funzioni non possono restituire i tipi di array, il che va bene poiché le espressioni di array non possono essere neppure la destinazione di un compito.

Il metodo più sicuro è che il chiamante definisca l'array e passi il suo indirizzo e dimensione alla funzione che dovrebbe scrivergli:

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);
  ...
}

Un altro metodo è per la funzione di allocare dinamicamente l'array e restituire il puntatore e la dimensione:

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);
  ...
}

In questo caso, il chiamante è responsabile della deallocazione dell'array con la freefunzione di libreria.

Si noti che dstnel codice sopra è un semplice puntatore a char, non un puntatore a una matrice di char. La semantica del puntatore e dell'array di C è tale che è possibile applicare l'operatore di sottoscrizione []a un'espressione di tipo di array o tipo di puntatore; entrambi src[i]e dst[i]accederanno iall'elemento th dell'array (anche se srcha solo il tipo di array).

È possibile dichiarare un puntatore ad un array N-elemento Te fare qualcosa di simile:

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]);
  ...
}

Diversi inconvenienti con quanto sopra. Innanzitutto, le versioni precedenti di C prevedono SOME_SIZEdi essere una costante di tempo di compilazione, il che significa che la funzione funzionerà solo con una dimensione dell'array. In secondo luogo, devi dereferenziare il puntatore prima di applicare il pedice, che ingombra il codice. I puntatori agli array funzionano meglio quando si ha a che fare con array multidimensionali.


2
Il tuo link allo "sviluppo di C" si è rotto ... sembra che dovrebbe indirizzarci qui: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@Kundor: Ciò che barriceve è un puntatore, non un array. Nel contesto di una dichiarazione di parametro funzione, T a[N]e T a[]sono entrambi trattati come T *a.
John Bode,

@JohnBode: hai ragione! Per qualche motivo, ho pensato che le matrici di dimensioni fisse fossero passate sulla pila. Ricordo un'occasione, molti anni fa, quando ho scoperto che la dimensione di un array doveva essere specificata nella firma del parametro, ma dovevo essere confuso.
Nick Matteo,

@JohnBode, nella prima riga della seconda parte del codice: l' void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)ultimo parametro dovrebbe essere nel size_ttipo no char.
Seyfi,

11

Non sto dicendo che questa è la soluzione migliore o una soluzione preferita al problema dato. Tuttavia, può essere utile ricordare che le funzioni possono restituire le strutture. Sebbene le funzioni non possano restituire le matrici, le matrici possono essere avvolte in strutture e la funzione può restituire la struttura portando così l'array con sé. Funziona con matrici di lunghezza fissa.

    #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 commenti sui punti di forza e di debolezza di questa tecnica. Non mi sono preoccupato di farlo.


1
non è chiaro il motivo per cui questa non è la risposta accettata. La domanda non era se è possibile restituire un puntatore a un array.
Frank Puck,

La memoria è allocata CHAR_ARRAY returnedsull'heap? Certamente non può essere nello stack (nel frame dello stack di returnArray()destra?
Minh Tran

9

Che ne dici di questa implementazione deliziosamente malvagia?

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;    \
}

main.c

#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;
}

2
Questo è diabolicamente delizioso da suscitare la mia curiosità. Puoi spiegare un po 'di più cosa hai fatto lassù o forse suggerire una lettura di questa prelibatezza che chiami? Grazie in anticipo.
Unheilig,

1
@Unheilig - Nota che ci sono alcuni potenziali bug in questo, era solo una prova di Concept. Detto questo, il trucco sta restituendo un structoggetto / contenitore array. Pensalo come un C ++ std :: vector. Il preprocessore espanderà la intversione di questo a struct intArray { int* contents; int size; };.
pyrospade,

1
Mi piace l'approccio. pro: questa è una soluzione generica; contra: soluzione intensiva di memoria. Non ottimale per vettori di dimensioni kown. Ad ogni modo, questo può essere aggiornato con allocazione iniziale delle dimensioni. Vorrei aggiungere un po 'di controllo di allocazione. Ottima proposta per iniziare :)
urkon,

Mix-mash prepossessing orientato agli oggetti. Mi piace.
Jack Giffin,

6

Nel tuo caso, stai creando un array nello stack e una volta che lasci l'ambito della funzione, l'array verrà deallocato. Invece, crea un array allocato dinamicamente e restituiscici un puntatore.

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);

}

2
Non c'è nessun newoperatore in C. Questo è C ++.
Eric Postpischil,

1
Ed sizeof(char)è garantito che lo sia 1, quindi in questo caso puoi lasciar perdere quel bit malloc.
Ed S.

ok quindi Se volessi stampare il contenuto del nuovo array, potrei semplicemente fare la mia dichiarazione 'printf' ma sostituire 'returnArray' con 'arr'?
user1506919

Non stai chiamando correttamente la funzione (solo un argomento quando la firma richiede due).
Ed S.

Stai passando &arr. Vuoi arressere un char *e passarlo usando arr.
chris,

4

Puoi farlo usando la memoria heap (tramite invocazione di malloc ) come altre risposte riportate qui, ma devi sempre gestire la memoria (usa la funzione free () ogni volta che chiami la tua funzione). Puoi anche farlo con un array statico:

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

// do something in your array here

return array; 
}

È possibile utilizzarlo senza preoccuparsi della gestione della memoria.

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

In questo esempio è necessario utilizzare una parola chiave statica nella definizione dell'array per impostare la durata dell'array sull'applicazione, in modo che non venga distrutta dopo l'istruzione return. Naturalmente, in questo modo occupi SIZE byte nella tua memoria per l'intera vita dell'applicazione, quindi ridimensionalo correttamente!


2

Il metodo restituirà una variabile di stack locale che non funzionerà correttamente. Per restituire un array, crearne uno esterno alla funzione, passarlo per indirizzo nella funzione, quindi modificarlo o creare un array sull'heap e restituire quella variabile. Entrambi funzioneranno, ma il primo non richiede alcuna allocazione dinamica della memoria per farlo funzionare correttamente.

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);
}

0

Puoi usare il codice in questo modo:

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.
}

Quando lo fai, la memoria dovrebbe essere successivamente liberata, passando l'indirizzo per liberarlo.

Ci sono altre opzioni Una routine potrebbe restituire un puntatore a un array (o parte di un array) che fa parte di una struttura esistente. Il chiamante potrebbe passare un array e la routine si limita a scrivere nell'array anziché allocare spazio per un nuovo array.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.