Provenendo da Python, se C non ha limiti di matrice, come fa a sapere dove inizia [1]?
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
b = a[1][1];
Provenendo da Python, se C non ha limiti di matrice, come fa a sapere dove inizia [1]?
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
b = a[1][1];
C conosce il limite di un array in tempo di compilazione , al contrario di Python che conosce il limite di un array in runtime .
Il compilatore fondamentalmente riscrive l'accesso dell'array nelle operazioni del puntatore. Sa al momento della compilazione che la dimensione di una prima dimensione dell'array è int[3]
, cioè sizeof(int)*3
. Quindi:
b = a[x][y];
Viene sostanzialmente riscritto in
b = *(a + x * 3 + y);
Quindi la conoscenza della dimensione (il numero 3) è codificata nel codice, anche se le dimensioni non sono esplicitamente disponibili in fase di runtime. Probabilmente, gli array sono puramente un costrutto in fase di compilazione in C.
C memorizza il tuo array 2D come un blocco contiguo di 9 numeri interi:
1 2 3 4 5 6 7 8 9
C utilizza il tipo di tempo di compilazione e le informazioni sulle dimensioni per generare l'aritmetica del puntatore per accedere agli elementi.
Nel tuo esempio a[i]
è un puntatore all'inizio della riga i
-th. Sapendo che a è un array 2D di int
, e sapendo che l'ultima dimensione ha una dimensione di 3
, può determinare che a[i]
è un array di 3
int
a partire da i*3
th intero in a.
1 2 3 4 5 6 | 7 8 9 |
^
Start of a[2]
Allo stesso modo, per accedere a a[i][j]
il compilatore genererà il codice che usa il 3*i+j
th intero in a.
Tuttavia C ritiene che il programmatore sappia cosa sta facendo. Non controllerà i limiti. Se i
sarebbe 5 e j
120, il codice verrebbe eseguito alla cieca, causando comportamenti indefiniti come ad esempio corruzione della memoria, core dump e altre cose brutte.
Quindi se i limiti sono importanti per il tuo algoritmo, dovresti utilizzare limiti fissi o passare i limiti come parametro aggiuntivo alla tua funzione.
Se dovessi usare C ++ anziché C, potresti usare i vettori, che sono matrici dinamiche che ne conoscono le dimensioni (ma sarebbe comunque compito tuo controllare i limiti)
C sa perché conosce la dimensione di un int
. Quindi array[1]
inizia una dimensione int in aumento da array[0]
. Lo stesso vale per array[1][1]
| size of an int |
01011101 01101110 10...
^ ^
array[0] array[1]
Si noti che la dimensione di un int dipende dalla piattaforma, questo era solo un esempio.
C sa dove si trova il successivo spazio di archiviazione per un determinato tipo per l'imballaggio al momento della dichiarazione, quindi se si dispone di un array di interi a 16 bit l'indice "successivo" in un array a dimensione singola sarà, di solito, due byte indirizzo più alto ma su alcune macchine e con alcune impostazioni del compilatore potrebbero essere i primi 2 byte della successiva parola a 64 bit.
Per un array 2D il successivo nell'altro senso normalmente sarebbe la lunghezza della singola, inferiore, dimensione come dichiarato volte il passo per i membri adiacenti dell'array di ordine inferiore, possibilmente più qualche padding .
Per un array 3D, il prossimo elemento nella 3a Dimensione dovrebbe normalmente essere la dimensione dell'array 2D nell'ordine inferiore, possibilmente ancora più un padding.
La buona notizia è che il compilatore si prende cura di questo se si utilizzano i normali meccanismi di indicizzazione, il male è che, poiché il meccanismo non è specificato nella specifica ANSI, spetta al produttore del compilatore e come questo è fatto e / o controllato. Se si decide di provare ad accedere direttamente ai dati in tali strutture, è probabile che si verifichino problemi importanti. Come risultato di questa ambiguità di memorizzare tali dati o di scambiarli tra programmi che sono eventualmente compilati con un compilatore diverso, o anche lo stesso compilatore ma con impostazioni diverse , su una singola macchina diventa un incubo e dati problemi come la dimensione dei componenti di base e l'endianness che scambiano dati tra macchine o persino tempi lo diventano ancora di più.
Questo è il motivo per cui le buone specifiche di archiviazione e scambio dei dati sono così prolisse e spesso includono la necessità di impacchettare i dati in strutture specifiche.
Ho avuto un problema diversi anni fa in cui i dati venivano ricevuti come bitfield lungo 24 bit (in un'applicazione di telecomunicazioni), ma sulla macchina Solaris che usavamo, con il compilatore predefinito, anziché essere lunghi 3 byte questa struttura era 192 byte. Questo perché, per la velocità, persino un int: 1 è stato memorizzato in una parola a 64 bit. Diverse ore di ricerche sulle impostazioni del compilatore hanno mostrato che il controllo di questa modifica tra le versioni del compilatore e alla fine passavamo a gcc. Consulta il manuale di gcc per gli attributi di tipo qui per ulteriori informazioni.
Tieni presente che C non ha di per sé alcun limite di array e non tutti i compilatori controllano le dimensioni dell'array. Di conseguenza se un array è definito come int MyArray[8][12][20];
in un modulo ma un altro lo mostra come extern int MyArray[];
, il secondo modulo imposterà felicemente MyArray[50000] = 0
con i problemi risultanti.