RISOLTO Gestione Struct a basso livello

Stato
Discussione chiusa ad ulteriori risposte.

Matteo34

Nuovo Utente
104
3
CPU
i5-10500 3.2Ghz
Dissipatore
Non specificato
Scheda Madre
Non specificata
HDD
M.2 251GB e M.2 500GB
RAM
16GB DDR4 2666mhz
GPU
Grafica Intel® UHD 630
Audio
Non specificata
Monitor
1920x1080 27"
PSU
Non specificato
Case
Non specificato
Periferiche
Nono specificato
Net
Eolo
OS
Ubuntu
Ciao a tutti volevo sapere come venivano allocate le struct in memoria.
C:
#include<stdio.h>
struct prova{
    int a;
    int b;
    int c;
};
int main(void)
{
    struct prova x;
}
Codice:
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, 0
        pop     rbp
        ret
Dal disassemblato non viene allocato niente quando dichiaro una variabile di tipo "struct prova", ma se inizializzo un campo della struct, viene inizializzato con il valore il contenuto dell'indirizzo di [rbp - 12], quindi viene allocato spazio in memoria, e allora perché quando dichiaro una variabile struct il disassemblato non tiene traccia dell'allocazione:
C:
#include<stdio.h>
struct prova{
    int a;
    int b ;
    int c;
};
int main(void)
{
    struct prova x;
    x.a = 5;
}
Codice:
main:
        push    rbp
        mov     rbp, rsp
       ___________________________________
       | mov     DWORD PTR [rbp-12], 5|
       ---------------------------------
        mov     eax, 0
        pop     rbp
        ret
Quindi le mie domande sono:
Vorrei sapere come viene allocata la memoria di una struct e perché le variabili vengono allocate al contrario rispetto alla dichiarazione(da quel che ho visto dal disassemblato viene allocata prima c poi d e poi a)?
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,853
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Le struct sono un'astrazione. Il tuo codice oltretutto si presta anche a probabili ottimizzazioni "importanti", in quanto stai utilizzando solo un campo della tua struct.
La struct risiede in memoria sullo stack, oppure no, dipende. Dal codice che hai prodotto infatti il valore viene memorizzato alla locazione RBP-12. Di norma riconosci una struttura dati poichè in strutture più complesse viene usato un registro che fa da base, e poi si somma un offset per leggere/scrivere un elemento.
Se hai un array di struct, per andare al prossimo elemento vedrai che probabilmente verrà incrementato quel puntatore della dimensione della struttura.

Non puoi aspettarti che tutto ciò che scrivi venga riprodotto "letteralmente": C è un linguaggio compilato, e il compilatore tra le sue fasi ha l'ottimizzazione. Questa fase di solito modifica in maniera anche - a volte - difficilmente comprensibile quello che scrivi.
Visto che nel tuo caso con la struct non stavi facendo nulla, non è stato assegnato nulla a nessuna locazione sullo stack; la mov che riguarda EAX è il ritorno dalla funzione.

Ho preso il tuo esempio e l'ho reso un pò più completo:

C:
#include<stdio.h>
struct prova{
    int a;
    int b ;
    int c;
};

int main(void)
{
    struct prova x;
    x.a = 5;
    x.b = 12;
    x.c = 7;
  
    printf("%d, %d, %d\n", x.a, x.b, x.c);
  
    return 0;
}

La parte che inizializza la struttura e stampa è questa:
Codice:
mov dword ptr ss:[rsp+20],5
mov dword ptr ss:[rsp+24],C
mov dword ptr ss:[rsp+28],7
mov r9d,dword ptr ss:[rsp+28]
mov r8d,dword ptr ss:[rsp+24]
mov edx,dword ptr ss:[rsp+20]
lea rcx,qword ptr ds:[7FF6637AC000]
call struct.7FF6637910C0

Come vedi vengono inizializzati uno di seguto all'altro.

EDIT:

Aggiungo un altro esempio:

C:
#include<stdio.h>
struct prova{
    int a;
    int b ;
    int c;
};

int main(void)
{
    struct prova x[2];
    for(int i=0; i<2; i++) {
        x[i].a = 5 + i;
        x[i].b = 12 + i;
        x[i].c = 7 + i;          
    }
  
    for(int i=0; i<2; i++)
        printf("%d, %d, %d\n", x[i].a, x[i].b, x[i].c);
  
    return 0;
}

Questo è ciò che accade quando viene ottimizzato...

Codice:
mov dword ptr ss:[rsp+20],0
jmp struct.7FF7340E1027
mov eax,dword ptr ss:[rsp+20]
inc eax
mov dword ptr ss:[rsp+20],eax
cmp dword ptr ss:[rsp+20],2
jge struct.7FF7340E106C
mov eax,dword ptr ss:[rsp+20]
add eax,5
movsxd rcx,dword ptr ss:[rsp+20]
imul rcx,rcx,C
mov dword ptr ss:[rsp+rcx+28],eax
mov eax,dword ptr ss:[rsp+20]
add eax,C
movsxd rcx,dword ptr ss:[rsp+20]
imul rcx,rcx,C
mov dword ptr ss:[rsp+rcx+2C],eax
mov eax,dword ptr ss:[rsp+20]
add eax,7
movsxd rcx,dword ptr ss:[rsp+20]
imul rcx,rcx,C
mov dword ptr ss:[rsp+rcx+30],eax
jmp struct.7FF7340E101D

In questo caso il compilatore "sà" che il ciclo è di soli 2 elementi. Ha scelto di non fare l'unrolling, ma il codice non è comunque "immediato".

Compilando con /O2 invece viene tradotto in questo modo:
Codice:
mov edi,2
mov dword ptr ss:[rsp+34],8
mov r9d,dword ptr ds:[rbx+4]
lea rcx,qword ptr ds:[7FF71E9322E0]
mov r8d,dword ptr ds:[rbx]
mov edx,dword ptr ds:[rbx-4]
call struct.7FF71E921090
lea rbx,qword ptr ds:[rbx+C]
sub rdi,1
jne struct.7FF71E921051
xor eax,eax
mov rcx,qword ptr ss:[rsp+38]
xor rcx,rsp
call struct.7FF71E921170

Qui RBX punta all'indirizzo in memoria dove risiete il valore 5. Gli altri cinque valori sono posizionati in memoria in seguito a quello.


PS: visto con target x64 è comunque ancora più complesso; se compili per x86 è più semplice da leggere
 
Ultima modifica:

Andretti60

Utente Èlite
6,440
5,091
Quando definisci una struct il compilatore non alloca proprio nulla, è solo una definizione. È solo quando dichiari una variabile che il compilatore ne alloca lo spazio. Se la variabile è globale lo spazio è nello heap, altrimenti nello stack, e nello stack lo spazio viene riservato solo se la variabile viene poi usata. Ma come la memoria viene gestita dipende molto dal compilatore, in quanto lo standard non lo definisce. Certi compilatori quando la memoria da allocare è tanta (come per esempio grosse struct e vettori) preferisco usare lo heap anche per variabili locali, per evitare stack overflow.
 

pabloski

Utente Èlite
2,868
916
perché le variabili vengono allocate al contrario rispetto alla dichiarazione

sulle altre cose ti è stato già risposto, compreso il perchè in quell'assembly non c'è traccia della struct ( il compilatore è intelligente e vede che non la usi e quindi non l'alloca nemmeno...e i compilatori moderni fanno cose da pazzi, spesso introducendo bug e vulnerabilità nel codice!! )

sul perchè siano al rovescio, è perchè lo stack cresce al rovescio
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,853
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
sul perchè siano al rovescio, è perchè lo stack cresce al rovescio
Solo una cosa: non sono al rovescio in realtà. Non lo sono innanzitutto perchè ha solo 1 valore sullo stack e poi perchè l'inizializzazione è comunque sequenziale.
In quello che ho compilato io infatti sono in sequenza:

Codice:
mov dword ptr ss:[rsp+20],5
mov dword ptr ss:[rsp+24],C
mov dword ptr ss:[rsp+28],7

Non è come il push dei parametri sullo stack per la call ad una procedura, dove trovi prima l'ultimo parametro e poi il primo.
 
U

Utente cancellato 371741

Ospite
Oltre al fatto che possono essere allocate nello stack, o dinamicamente in runtime nell'heap, o anche come globali statiche o magari costanti, quindi come costanti non le troverai piu in .data o .bss ma nella sezione .text.

In piu si potrebbe aggiungere che la dimesnione totale allocata ovviamente dipende dall'architettura e dai data type specifici di quell' architettura. E non solo, certe architetture che originariamente, come ARM, rispettavano allineamenti di memoria a 4 bytes usavano 32 bit anche per variabili char da 1 byte, dunque guardando la struttura in memoria ci si puo confondere. "pragma pack" serve ad assicurarsi che la struttura non contenga appunto "cassetti" vuoti.
 
Ultima modifica da un moderatore:

Matteo34

Nuovo Utente
104
3
CPU
i5-10500 3.2Ghz
Dissipatore
Non specificato
Scheda Madre
Non specificata
HDD
M.2 251GB e M.2 500GB
RAM
16GB DDR4 2666mhz
GPU
Grafica Intel® UHD 630
Audio
Non specificata
Monitor
1920x1080 27"
PSU
Non specificato
Case
Non specificato
Periferiche
Nono specificato
Net
Eolo
OS
Ubuntu
sulle altre cose ti è stato già risposto, compreso il perchè in quell'assembly non c'è traccia della struct ( il compilatore è intelligente e vede che non la usi e quindi non l'alloca nemmeno...e i compilatori moderni fanno cose da pazzi, spesso introducendo bug e vulnerabilità nel codice!! )

sul perchè siano al rovescio, è perchè lo stack cresce al rovescio
Se dichiaro un array, quest'ultimo viene allocato partendo da indirizzi bassi fino a indirizzi alti:
C:
#include<stdio.h>

int main(void)
{
    int a[3]={1, 2, 3};
    return 0;
}
Disasemblato:
Codice:
main:
        push    rbp
        mov     rbp, rsp
        ___________________________________
        | mov     DWORD PTR [rbp-12], 1|
        | mov     DWORD PTR [rbp-8], 2 |
        | mov     DWORD PTR [rbp-4], 3 |
        ---------------------------------
        mov     eax, 0                             
        pop     rbp
        ret
Il primo elemento si trova in un indirizzo più basso rispetto all'ultimo.
Adesso la mia domanda era, perché nelle struct l'ultima variabile a un indirizzo minore rispetto alla prima?
C:
#include<stdio.h>
struct prova{
    int a;
    int b;
    int c;
};
int main(void)
{
    struct prova x;
    x.a = 6;
    x.b = 7;
    x.c = 8;
}
Dissasemblato:
Codice:
main:
        push    rbp
        mov     rbp, rsp
        __________________________________
        |mov     DWORD PTR [rbp-12], 6|
        |mov     DWORD PTR [rbp-8], 7  |
        |mov     DWORD PTR [rbp-4], 8  |
        ---------------------------------
        mov     eax, 0
        pop     rbp
        ret
Quindi per quale motivo se dichiaro una variabile struct, i suoi campi vengono allocati da indirizzi bassi a indirizzi alti come un array, e non come delle semplici variabili allocate sullo stack come:
C:
int main(void)
{
    int a = 5;
    int b = 6;
    int c = 7;
}
Dissasemblato:
Codice:
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 5
        mov     DWORD PTR [rbp-8], 6
        mov     DWORD PTR [rbp-12], 7
        mov     eax, 0
        pop     rbp
        ret
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,853
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Adesso la mia domanda era, perché nelle struct l'ultima variabile a un indirizzo minore rispetto alla prima?

Stai usando GCC (MinGw sotto windows)? Quale versione?
 

Matteo34

Nuovo Utente
104
3
CPU
i5-10500 3.2Ghz
Dissipatore
Non specificato
Scheda Madre
Non specificata
HDD
M.2 251GB e M.2 500GB
RAM
16GB DDR4 2666mhz
GPU
Grafica Intel® UHD 630
Audio
Non specificata
Monitor
1920x1080 27"
PSU
Non specificato
Case
Non specificato
Periferiche
Nono specificato
Net
Eolo
OS
Ubuntu

pabloski

Utente Èlite
2,868
916
Adesso la mia domanda era, perché nelle struct l'ultima variabile a un indirizzo minore rispetto alla prima?

Per le variabili singole, lui guarda la prima variabile, ne misura la dimensione e la sottrae ad esp. Poi piazza il contenuto della variabile nell'area allocata. E così via.

Mentre considera l'array e la struct come elemento singolo ( singola variabile ). E le allocazioni degli elementi seguono l'ordine naturale, cioè dall'indirizzo inferiore a quello superiore. Cioè calcola la dimensione della struct ( la somma delle dimensione dei suoi capmi ). Idem per l'array, dove fa la somma delle dimensioni di tutti gli elementi. Sottrae questa dimensione ad esp e comincia ad allocare da esp+0, poi esp+dim_elem, esp+dim_elem*2, ecc...

Solo che lo fa usando ebp e sottraendo, invece di usare esp e sommando. E questa cosa crea confusione.
 
Stato
Discussione chiusa ad ulteriori risposte.

Ci sono discussioni simili a riguardo, dai un'occhiata!

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!

Discussioni Simili