Restituisce puntatori di array e popola un array inserito come parametro?

2

Quale è meglio? Ho notato che quest'ultimo è usato in molti codici C. Le persone tipicamente mallocano un array e quindi lo passano come parametro a una funzione, che lo popolerà. Mentre in Java, il primo sembra più popolare.

È uno migliore dell'altro? In caso contrario, le funzionalità della lingua richiedono una preferenza?

    
posta user3724404 07.09.2017 - 16:26
fonte

3 risposte

5

Nel classico C, restituire un array da una funzione non è così facile come in Java. Ecco perché le funzioni C spesso scelgono di popolare una matrice passata dal chiamante, mentre i metodi Java in genere seguono il flusso naturale di dati, cioè restituiscono i risultati come array.

Quindi in Java è possibile (e dovrebbe) seguire il flusso naturale di dati, cioè restituire una nuova matrice dal metodo. In C, anche se è contro-intuitivo, è spesso meglio avere il chiamante fornire l'array - I programmatori C sono abituati a quello stile.

Lascia che ti spieghi il mio ragionamento:

Stile C

Se vuoi restituire un array da una funzione, devi allocarlo dinamicamente (non può essere in pila in quanto non sopravviverebbe lasciando la funzione, e non può essere statico, perché in seguito multiplo le chiamate di funzione verrebbero confuse). Per il chiamante, ciò significa che si assume la responsabilità di liberare finalmente l'array.

Il chiamante deve sapere quanto è lungo l'array, quindi non accede agli elementi non validi. Poiché un risultato di matrice (ad esempio int[] ) è in realtà solo un puntatore al suo tipo di elemento ( int * ), non c'è posto per comunicare la lunghezza.

Quindi in C, non esiste un modo semplice e naturale per restituire un array da una funzione.

(Naturalmente, questa è solo la classica C, e le versioni successive hanno aggiunto molte delle funzionalità di cui la classica C mancava, ma molte funzioni della libreria erano già state definite presto ...).

Stile Java

Gli array vengono sempre creati nella memoria dinamica (nell'heap), ma sono gestiti dalla JVM. Quindi il chiamante non ha il dovere di liberare memoria, perché è fatto dal garbage collector.

Inoltre non ci sono problemi con la lunghezza dell'array dato che gli array Java ne conoscono la lunghezza.

    
risposta data 07.09.2017 - 17:43
fonte
5

Da una prospettiva semantica, restituire una matrice dal metodo suggerisce che si tratta di una matrice diversa da quella che hai passato. Ecco perché lo stile C accettato, quando si modifica una matrice sul posto, è modifica l'array che è passato, in posto. Il ritorno di una nuova nuova è più comune in Java che in C, motivo per cui si vede che viene passato da la funzione come valore di ritorno.

Un array in C è in realtà solo un puntatore a un valore digitato; C non sa nemmeno per quanto tempo un array è se lo si è assegnato dinamicamente, rendendo difficile per un chiamante gestire un array restituito. Le matrici in Java contengono metadati, come la lunghezza dell'array e il tipo dei suoi membri, rendendo più facile per i metodi restituire nuovi array e per i chiamanti per consumarli.

Si noti che è ancora possibile modificare un array esistente sul posto in Java, se lo si desidera, nel qual caso si farebbe la stessa cosa in Java che si farebbe in C: modificare il parametro dell'array passato in- posto.

Puntatori e riferimenti Java non sono la stessa cosa, anche se sono usati per raggiungere obiettivi simili. In C, un puntatore è un indirizzo di memoria effettivo. L'aritmetica del puntatore è possibile in C, dove viene effettivamente utilizzata per allocare i membri dell'array di memoria e di dereference e membri della struct.

In Java, i riferimenti sono un dettaglio di implementazione, non un indirizzo di memoria. Non puoi eseguire aritmetica del puntatore su di essi, né puoi fare alcuna ipotesi su come funzionano sotto il cofano da una memoria prospettiva, perché le specifiche del linguaggio Java non stabiliscono il modo in cui devono essere implementate.

Questa differenza viene ulteriormente evidenziata nel trattamento degli array a lunghezza zero: non esistono in C , tranne come ultimo membro di una struct.

    
risposta data 07.09.2017 - 17:08
fonte
0

do language features invoke a preference?

Assolutamente sì. A causa del modo in cui C tratta le espressioni dell'array, passare un array già assegnato (statico, automatico o dinamico) come parametro a una funzione è lontano più preferibile all'array allocare spazio e restituire un puntatore al chiamante.

In primo luogo, le basi:

Tranne quando è l'operando degli operatori sizeof o unary & , o è una stringa letterale usata per inizializzare un'altra matrice in una dichiarazione, un'espressione di tipo "N-elemento array of T "sarà convertito (" decay ") in un'espressione di tipo" puntatore a T ", e il valore dell'espressione sarà l'indirizzo del primo elemento dell'array 1 . IOW, le espressioni degli array perdono il loro "array-ness" nella maggior parte dei casi.

Quando si chiama una funzione con un parametro dell'array, come

int arr[N];
...
foo( arr );

l'espressione arr nella chiamata a foo "decadera" dal tipo int [N] a int * e cosa foo riceve sarà un valore puntatore, non un oggetto matrice :

void foo( int *arr ) // T a[N] and T a[] are treated as T *a
{                    // in a function parameter declaration
  ...
}

Non è possibile restituire un oggetto oggetto di una matrice - C non consente alle funzioni di restituire i tipi di array (un prototipo come int foo()[10] non è consentito). A causa della regola di conversione di cui sopra, return arr; termina restituendo un puntatore al primo elemento:

int *foo( void )
{
  int arr[N];
  ...
  return arr; // equivalent to return &arr[0]
}

Eccetto arr cessa di esistere una volta che foo esce 2 , in modo che il valore del puntatore restituito sia non valido , e tentando di dereferenziare porterà a un comportamento indefinito.

A questo punto, hai due scelte: rendere la funzione responsabile per l'allocazione dinamica del buffer di destinazione e il rinvio del risultato al chiamante, oppure rendere il chiamante responsabile per allocare il buffer di destinazione (dinamicamente o meno) e passarlo a la funzione.

Ricorda che lo standard C non definisce alcun tipo di garbage collection automatico per la memoria dinamica: tutta la memoria dinamica deve essere esplicitamente deallocata dalle chiamate a free .

La prima opzione divide i compiti di gestione della memoria tra la funzione e il chiamante, il che non è desiderabile. Chiunque usi la funzione deve essere consapevole che la funzione sta allocando dinamicamente memoria e che è responsabile di liberare quella memoria quando ha finito con essa. Se l'utente della funzione non presta attenzione alla documentazione e non libera la memoria restituita dalla funzione, si verifica una perdita di memoria.

La seconda opzione mette tutta la responsabilità della gestione della memoria su una parte. La funzione chiamata ottiene un puntatore e la dimensione massima del buffer - non deve preoccuparsi se la memoria puntata è staticamente, automaticamente o allocata dinamicamente. Questo metodo è più sicuro e più robusto, motivo per cui lo si vede più spesso nel codice C.

In Java, gli array sono trattati allo stesso modo di quasi tutti gli altri tipi di dati (non "decadono" in un tipo di puntatore), quindi è più naturale che la funzione restituisca semplicemente una matrice al chiamante. Anche se gli array vengono allocati dall'heap quando vengono creati, non devi preoccuparti di disassegnarli quando hai finito: verranno automaticamente raccolti tramite garbage quando il loro conteggio dei riferimenti va a 0.

  1. "Un array è solo un puntatore" è un'errata affermazione di questa regola. Le matrici non sono . Le espressioni della matrice vengono convertite nelle espressioni del puntatore nella maggior parte delle circostanze, ma un oggetto non è un puntatore.
  2. Se dichiariamo arr con la parola chiave static , verrà mantenuta dopo la chiusura della funzione, ma il codice non sarà più rientrante.

risposta data 08.09.2017 - 17:15
fonte

Leggi altre domande sui tag