Se siamo in grado di utilizzare i puntatori e malloc
per creare e utilizzare gli array, perché il tipo di matrice esiste in C? Non è inutile se invece possiamo usare i puntatori?Perché C ha bisogno di matrici se ha puntatori?
risposta
Gli array sono più veloci dell'allocazione della memoria dinamica.
Gli array sono "allocati" in "tempo di compilazione" mentre malloc assegna in fase di esecuzione. L'allocazione richiede tempo.
Inoltre, C non specifica che malloc()
e gli amici siano disponibili nelle implementazioni indipendenti.
Modifica
Esempio di matrice
#define DECK_SIZE 52
int main(void) {
int deck[DECK_SIZE];
play(deck, DECK_SIZE);
return 0;
}
Esempio di malloc()
int main(void) {
size_t len = 52;
int *deck = malloc(len * sizeof *deck);
if (deck) {
play(deck, len);
}
free(deck);
return 0;
}
Nella versione matrice, lo spazio per l'array deck
è stato riservato dal compilatore quando il il programma è stato creato (ma, naturalmente, la memoria è riservata/occupata solo durante l'esecuzione del programma), nella versione malloc()
, lo spazio per l'array deck
deve essere richiesto ad ogni esecuzione del programma.
Gli array non possono mai cambiare dimensione, la memoria di malloc'd può crescere quando necessario.
Se è necessario un numero fisso di elementi, utilizzare un array (entro i limiti dell'implementazione). Se è necessaria memoria che può aumentare o diminuire durante l'esecuzione del programma, utilizzare malloc()
e gli amici.
L'allocazione di entrambi avviene in fase di esecuzione. La differenza è che C consente di fornire la dimensione in fase di esecuzione per uno, ma non per l'altro. – AraK
Puoi darmi maggiori dettagli su cosa succede in fase di compilazione e in fase di esecuzione quando si trova un array? Anche un esempio sarebbe utile. Grazie. – root
@AraK, penso per allocazione in fase di esecuzione, vuol dire che il software deve richiedere la memoria dal sistema operativo (con la chiamata a 'malloc'), invece di regolare semplicemente lo stack-pointer (e quindi" assegnato " a "tempo di compilazione"). – mrduclaw
Gli array hanno i loro usi e dovrebbero essere utilizzati quando è possibile, poiché l'allocazione statica contribuirà a rendere i programmi più stabili e, a volte, a causa della necessità di garantire che le perdite di memoria non si verifichino.
Esistono perché alcuni requisiti li richiedono.
In un linguaggio come BASIC, si dispone di determinati comandi consentiti, e questo è noto, a causa del linguaggio costrutto. Quindi, qual è il vantaggio di usare malloc per creare gli array e quindi riempirli con le stringhe?
Se devo definire comunque i nomi delle operazioni, perché non inserirli in un array?
C è stato scritto come linguaggio generico, il che significa che dovrebbe essere utile in qualsiasi situazione, quindi hanno dovuto assicurarsi che i costrutti fossero utili per scrivere sistemi operativi e sistemi incorporati.
Una matrice è un modo abbreviato per specificare, ad esempio, il puntamento all'inizio di un malloc.
Ma, immaginate di provare a fare matematica con le matrici usando le manipolazioni del puntatore invece di vec[x] * vec[y]
. Sarebbe molto difficile trovare errori.
Non è una cattiva domanda. Infatti, all'inizio C non aveva tipi di array.
Gli array globali e statici sono allocati in fase di compilazione (molto veloce). Altri array vengono allocati nello stack in fase di esecuzione (veloce). L'allocazione della memoria con malloc (da utilizzare per un array o altro) è molto più lenta. Una cosa simile si vede nella deallocazione: la memoria allocata dinamicamente è più lenta da deallocare.
La velocità non è l'unico problema. I tipi di array vengono automaticamente rilasciati quando escono dal campo di applicazione, pertanto non possono essere "filtrati" per errore. Non devi preoccuparti di liberare accidentalmente qualcosa due volte, e così via. Inoltre, semplificano gli strumenti di analisi statica per rilevare i bug.
Si potrebbe sostenere che esiste la funzione _alloca()
che consente di allocare memoria dallo stack. Sì, non esiste il tecnico motivo per cui gli array sono necessari oltre _alloca()
. Tuttavia, penso che gli array siano più convenienti da usare. Inoltre, è più facile per il compilatore ottimizzare l'uso di un array rispetto a un puntatore con un valore di ritorno _alloca()
, poiché è ovvio quale sia l'offset di un array assegnato allo stack dal puntatore dello stack, mentre se _alloca()
viene trattato come un nero -box chiamata di funzione, il compilatore non può dire questo valore in anticipo.
EDIT, poiché Tsubasa ha chiesto maggiori dettagli su come si verifica questa allocazione:
In architetture x86, il registro ebp
riferisce normalmente stack frame della funzione corrente, e viene utilizzato per fare riferimento a variabili impilare allocate. Ad esempio, potresti avere uno int
situato a [ebp - 8]
e un array char
che va da [ebp - 24]
a [ebp - 9]
. E forse più variabili e matrici in pila. (Il compilatore decide come utilizzare lo stack frame in fase di compilazione.I compilatori C99 consentono di allocare stack di array di dimensioni variabili, si tratta solo di eseguire un po 'di lavoro in fase di esecuzione.)
In codice x86, gli offset del puntatore (come [ebp - 16]
) possono essere rappresentati in una singola istruzione. Abbastanza efficiente
Ora, un punto importante è che tutti variabili e array stack allocato nel contesto corrente vengono recuperati attraverso le compensazioni da un'unica registro . Se chiami malloc, c'è (come ho già detto) qualche overhead di elaborazione nel trovare effettivamente un po 'di memoria per te. Ma anche, malloc ti dà un nuovo indirizzo di memoria. Diciamo che è memorizzato nel registro ebx
. Non è più possibile utilizzare un offset da ebp
perché non è possibile stabilire quale sarà l'offset in fase di compilazione. Quindi stai praticamente "sprecando" un registro extra di cui non avresti bisogno se invece usassi un array normale. Se si malloc più array, si hanno più valori del puntatore "imprevedibili" che ingrandiscono questo problema.
"preoccuparsi di liberare accidentalmente qualcosa due volte" - l'eliminazione di un puntatore nullo è un no-op come definito dallo standard, vale a dire, è sicuro. Ora, il fatto che si stia chiamando delete su un puntatore nullo probabilmente indica un altro problema, ma la chiamata stessa è sicura. –
@Ed Swangren: mentre una pratica comune di codifica è impostare le variabili del puntatore su NULL dopo averle liberate: A) non tutti lo fanno, e B) in strutture dati che possono avere puntatori multipli che puntano alla stessa cosa, ciò non può impedire il problema – Artelius
gli array multidimensionali sembrano essere difficili da realizzare con qualcosa come '_alloca', anche se senza un sovraccarico che C non era pronto a prendere. Anche 'sizeof' non sarebbe altrettanto facile da implementare per produrre un risultato in tempo di compilazione (il compilatore dovrebbe fondamentalmente fare il back-track del puntatore per vedere quale inizializzatore è stato usato per quel puntatore e poi decidere se fornire la dimensione di il puntatore, o "array") –
Gli array sono un buon miglioramento della sintassi rispetto ai puntatori. È possibile commettere errori di ogni genere senza saperlo quando si ha a che fare con i puntatori. Cosa succede se si spostano troppi spazi nella memoria perché si utilizza la dimensione byte errata?
In C, gli array e i puntatori utilizzano entrambi lo stesso metodo di calcolo del numero di byte da spostare. – Artelius
@Artelius, Niente affatto. Gli array usano il loro tipo per sapere quale offset utilizzare all'indicizzazione e il proprio indirizzo per conoscere l'indirizzo di base da avviare, mentre i puntatori utilizzano l'indirizzo memorizzato in essi per conoscere l'indirizzo di base e non conoscono gli offset (devi dillo loro manualmente). +1 :) –
@litb: Di cosa stai parlando? Array e puntatori * entrambi * usano la loro firma del tipo per sapere quale offset dall'indirizzo di base usare (anche se hai ragione che la fonte di questo indirizzo di base è diversa). – Artelius
Vedere this question discussing space hardening e C. Talvolta l'allocazione dinamica della memoria è solo una cattiva idea, ho lavorato con librerie C completamente prive di malloc() e amici.
Non si desidera che un satellite dereferenzia un puntatore NULL più di quanto si desideri che il software di controllo del traffico aereo dimentichi di azzerare i blocchi heap.
È inoltre importante (come altri hanno sottolineato) capire cosa fa parte di C e cosa lo estende in vari standard uniformi (ad esempio POSIX).
Explanation by Dennis Ritchie sulla storia C:
embrionali C
NB esisteva così brevemente che nessuna descrizione completa è stato scritto. Esso ha fornito i tipi int e char, array di loro, e puntatori a loro, hanno dichiarato in uno stile caratterizzato da
int i, j; char c, d; int iarray[10]; int ipointer[]; char carray[10]; char cpointer[];
La semantica di array è rimasto esattamente come in B e BCPL: le dichiarazioni di iarray e CArray creano cellule inizializzato dinamicamente con un valore che punta al primo di una sequenza di 10 interi e caratteri rispettivamente. Le dichiarazioni per ipointer e cpointer omettono le dimensioni, per affermare che nessuna memoria deve essere allocata automaticamente. All'interno delle procedure, l'interpretazione della lingua dei puntatori era identica a quella delle variabili dell'array: una dichiarazione puntatore creava una cella diversa da una dichiarazione di matrice solo in quanto il programmatore doveva assegnare un referente, invece di lasciare che il compilatore allocasse lo spazio e inizializza la cella.
I valori memorizzati nelle celle associate ai nomi di matrice e puntatore erano gli indirizzi macchina, misurati in byte, dell'area di memoria corrispondente. Pertanto, l'indirezione tramite un puntatore non implicava un sovraccarico del tempo di esecuzione per ridimensionare il puntatore dall'offset di parola a byte. D'altra parte, il codice macchina per l'indice della matrice e l'aritmetica del puntatore dipendevano ora dal tipo dell'array o dal puntatore: per calcolare iarray [i] o ipointer + i si implicava di ridimensionare l'addend i in base alla dimensione dell'oggetto a cui si riferiva.
Queste semantiche rappresentavano una facile transizione da B, e ho sperimentato con loro per alcuni mesi. I problemi divennero evidenti quando provai ad estendere la notazione del tipo, specialmente per aggiungere tipi strutturati (record). Le strutture, a quanto pareva, dovevano mappare in modo intuitivo sulla memoria della macchina, ma in una struttura contenente un array, non c'era un buon posto dove riporre il puntatore contenente la base dell'array, né alcun modo conveniente per sistemarlo inizializzato. Ad esempio, le voci di directory dei primi sistemi Unix possono essere descritti in C come
struct { int inumber; char name[14]; };
ho voluto la struttura non solo per caratterizzare un oggetto astratto, ma anche per descrivere un insieme di bit che possono essere letti da una directory. Dove potrebbe nascondere il puntatore il nome del puntatore al nome richiesto dalla semantica? Anche se le strutture sono state pensate in modo più astratto e lo spazio per i puntatori potrebbe essere nascosto in qualche modo, come potrei gestire il problema tecnico di inizializzare correttamente questi puntatori quando si assegna un oggetto complicato, forse uno che ha specificato strutture contenenti array contenenti strutture a profondità arbitraria?
La soluzione costituiva il salto cruciale nella catena evolutiva tra BCPL typless e typed C. Eliminava la materializzazione del puntatore nell'archiviazione e causava invece la creazione del puntatore quando il nome dell'array è menzionato in un'espressione. La regola, che sopravvive nel C di oggi, è che i valori del tipo di matrice vengono convertiti, quando appaiono in espressioni, in puntatori al primo degli oggetti che costituiscono l'array.
riassumere in parole mie - se name
sopra erano solo un puntatore, nulla di tutto ciò struct conterrebbe un puntatore aggiuntivo, distruggendo la perfetta mappatura di esso ad un oggetto esterno (come una voce della rubrica).
Cosa intendi per "tipo di matrice"? 'int x [6];'? – Jacob
Non sapevo che esistesse un tipo "array" in C. – mrduclaw
Invito questa domanda in più perché c'è una discussione interessante da trovare nelle risposte. – BobbyShaftoe