Da bar
è abbastanza grande il compilatore genera allocazione statica invece di assegnazione automatica in pila. Gli array statici vengono creati con la direttiva di assemblaggio .comm
che crea un'allocazione nella cosiddetta sezione COMMON. I simboli di quella sezione vengono raccolti, i simboli con lo stesso nome vengono uniti (ridotti a una richiesta di simboli con dimensioni uguali alla dimensione massima richiesta) e quindi ciò che è riposato viene mappato alla sezione BSS (dati non inizializzati) nella maggior parte dei formati eseguibili. Con gli eseguibili ELF la sezione .bss
si trova nel segmento dati, appena prima della parte del segmento dati dell'heap (c'è un'altra parte dell'heap gestita da mappature anonimi della memoria che non risiedono nel segmento dati).
Con il modello di memoria small
le istruzioni di indirizzamento a 32 bit vengono utilizzate per indirizzare i simboli su x86_64. Questo rende il codice più piccolo e anche più veloce. Alcuni uscita assemblaggio utilizzando small
modello di memoria:
movl $bar.1535, %ebx <---- Instruction length saving
...
movl %eax, baz_+4(%rip) <---- Problem!!
...
.local bar.1535
.comm bar.1535,2575411200,32
...
.comm baz_,12,16
Questo utilizza un'istruzione di movimento 32-bit (lunga 5 byte) per mettere il valore del simbolo bar.1535
(questo valore è uguale all'indirizzo della posizione simbolo) in i 32 bit inferiori del registro RBX
(i 32 bit superiori vengono azzerati). Il simbolo bar.1535
viene assegnato utilizzando la direttiva .comm
. La memoria per il blocco baz
COMMON viene assegnata in seguito. Poiché bar.1535
è molto grande, baz_
termina più di 2 GiB dall'inizio della sezione .bss
. Ciò pone un problema nella seconda istruzione poiché un offset non 32 bit (firmato) da RIP
deve essere utilizzato per indirizzare la variabile b
in cui deve essere spostato il valore di EAX
. Questo viene rilevato solo durante il tempo di collegamento. L'assemblatore stesso non conosce l'offset appropriato poiché non sa quale sarebbe il valore del puntatore di istruzioni (RIP
) (dipende dall'indirizzo virtuale assoluto in cui è caricato il codice e questo è determinato dal linker), quindi mette semplicemente un offset di 0
e quindi crea una richiesta di trasferimento di tipo R_X86_64_PC32
. Indica al linker di correggere il valore di 0
con il valore di offset reale. Ma non può farlo poiché il valore di offset non si adatta a un intero con segno a 32 bit e quindi bails out.
Con il modello medium
memoria in atto cose simile a questa:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
Prima di un'istruzione di movimento immediato a 64 bit (lungo 10 byte) viene usato per mettere il valore a 64 bit che rappresenta l'indirizzo bar.1535
nel registro R10
. La memoria per il simbolo bar.1535
viene allocata utilizzando la direttiva .largecomm
e termina quindi nella sezione .lbss
dell'esecutivo ELF. .lbss
viene utilizzato per memorizzare simboli che potrebbero non rientrare nei primi 2 GiB (e quindi non devono essere indirizzati utilizzando le istruzioni a 32 bit o l'indirizzamento relativo RIP), mentre gli oggetti più piccoli vanno a .bss
(baz_
è ancora assegnato utilizzando .comm
e non .largecomm
). Poiché la sezione .lbss
viene inserita dopo la sezione .bss
nello script del linker ELF, baz_
non risulta inaccessibile utilizzando l'indirizzamento RIP a 32 bit.
Tutte le modalità di indirizzamento sono descritte nello System V ABI: AMD64 Architecture Processor Supplement. È una lettura tecnica pesante, ma assolutamente da leggere per chiunque voglia veramente capire come funziona il codice a 64 bit sulla maggior parte degli Unix x86_64.
Quando un array ALLOCATABLE
si utilizza invece, gfortran
alloca memoria heap (probabilmente implementato come una mappa di memoria anonima data l'ampiezza della dotazione):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
Questo è fondamentalmente RDI = malloc(2575411200)
. Da allora in poi gli elementi di bar
sono accessibili utilizzando offset positivi dal valore memorizzato in RDI
:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
Per le posizioni che sono più di 2 GiB dall'inizio di bar
, un metodo più elaborato viene utilizzato. Per esempio. implementare b = bar(12,144*144*450)
gfortran
emette:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
Questo codice non è influenzato dal modello di memoria poiché nulla si presume sull'indirizzo dove l'allocazione dinamica sarebbe fatto. Inoltre, poiché l'array non viene passato in giro, non viene creato alcun descrittore. Se si aggiunge un'altra funzione che accetta una matrice a forma assunta e passa bar
ad esso, un descrittore per bar
viene creato come una variabile automatica (cioè sulla pila di foo
). Se la matrice è fatta statico con l'attributo SAVE
, il descrittore viene inserito nella sezione .bss
:
movl $bar.1580, %edi
...
; RAX still holds the address of the allocated memory as returned by malloc
; Computations, computations
movl -232(%rax,%rdx,4), %eax
movl %eax, baz_+4(%rip)
La prima mossa prepara l'argomento di una chiamata di funzione (nel mio caso campione call boo(bar)
dove boo
ha un'interfaccia che dichiara come un array di forma assunta). Sposta l'indirizzo del descrittore di array di bar
in EDI
. Questa è una mossa immediata a 32 bit, quindi il descrittore dovrebbe essere nei primi 2 GiB. Infatti, è allocato nel .bss
in entrambi i modelli small
e medium
memoria come questo:
.local bar.1580
.comm bar.1580,72,32
Questa è una spiegazione molto bella. Grazie. Questo mi dà un buon inizio per guardare molto più in profondità in un mucchio di questa roba (che è quello che stavo cercando). – mgilson
@ mgilson, solo per completezza della risposta, ho aggiunto anche spiegazioni su cosa succede quando 'bar' viene passato dal descrittore ad un'altra subroutine. –