1.06 Lezione 6 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki
📅 03-18 0:22
I registri da t0-t6 sono dei registri temporanei, e quindi il callee non è obbligato a preservarne il contenuto; traduzione: possiamo sporcarli come ci pare e piace.
D'altro canto, i registri s0-s11 sono registri save ed il loro contenuto deve essere preservato. Se il chiamante (callee) li utilizza, è obbligato a preservarne il contenuto, dobbiamo quindi salvare il contenuto dei registri save che utilizziamo nella funzione, attraverso lo stack (visto nella lezione precedente.)
Ovviamente iniziamo prima a scrivere il codice utilizzando i registri che ci servono, poi, a codice finito, ci curiamo di scrivere la parte di codice che salva i registri S.
Le procedure non foglia sono delle funzioni che chiamano altre funzioni.
int fact (int n){
if (n < 1) return 1;
else return n * fact(n - 1);
}
Codice scritto in C di un fattoriale ricorsivo
Quando non è strettamente necessario, è sempre meglio non utilizzare una funzione ricorsiva; questo per vari motivi, ma il più evidente lo vedremo tra poco con il codice assembly
fact:
addi sp, sp, -16 # facciamo spazio per salvare i due registri, ra e a0
sd ra, 8(sp)
sd a0, 0(sp)
addi t0, a0, -1
bge t0, zero, L1
addi a0, zero, 1
addi sp, sp, 16
jalr zero, 0(ra)
L1:
addi a0, a0, -1
jal ra, fact
addi t1, a0, 0
ld a0, 0(sp)
ld ra, 8(sp)
addi sp, sp, 16
mul a0, a0, t1
jalr zero, 0(ra)
Dobbiamo ovviamente salvare il return address, che verrà inevitabilmente sporcato nel momento in cui la funzione chiama un'altra funzione. Va ovviamente recuperato in uscita dallo stack.
Come detto precedentemente, nell'area Text è presente il codice del programma, posto solitamente agli indirizzi più bassi dopo la zona riservata al sistema operativo (non in foto, ma molto giù).
Successivamente sono presenti i dati static, e sono dati che rimangono presenti in memoria indipendentemente dal programma e dallo stack; ci sono infatti le variabili static, ovvero delle variabili dichiarate all'interno delle funzioni ma dichiarate come static var, in modo da allocarle nell'area static e non nello stack. Esempio
: voglio ad esempio contare il numero di volte in cui la funzione viene eseguita, non posso quindi utilizzare una variabile della funzione (con scope all'interno della funzione), ma una variabile static che viene incrementata ogni volta.
Questo tipo di variabile non può essere cancellata, inoltre tutte le stringhe vengono allocate in questa parte della memoria.
Possiamo anche dichiarare una funzione statica; questo serve a: quando vado a compilare diversi files (moduli) separati, ci serve a dire che una determinata funzione non deve essere vista ad altri moduli. Questo serve a prevenire a fare danni nel momento in cui due funzioni su due moduli diversi vengono dichiarati con lo stesso nome; queste funzioni non vengono esportate al linker, che ne ignorerà il nome.
Il modo classico di codificare i caratteri di testo, è quello di usare un singolo byte, quindi 8 bit. In questo byte vengono scritti i caratteri in codice ASCII, che permette di codificare 128 caratteri diversi, dove 95 sono grafici, e 33 di controllo.
operazioni di load
-
lb rd, offset(rs1)
8 bit -
lh rd, offset(rs1)
32 bit -
lw rd, offset(rs1)
64 bit
operazioni di load unsigned
Queste operazioni effettuano l'estensione del segno, se sto prendendo caratteri del set ASCII esteso, e voglio evitare il segno, devo usare queste operazioni. Possiamo quindi leggere un singolo carattere da una stringa, non usare lb!
lbu rd, offset(rs1)
lhu rd, offset(rs1)
lwu rd, offset(rs1)
operazioni di load
sb rs2, offset(rs1)
sh rs2, offset(rs1)
sw rs2, offset(rs1)
void strcpy (char x[], char y[]){
size_t i;
i = 0;
while ((x[i] = y[i]) != '\0') // assegno il carattere all'altro array finchè non trovo un terminatore (non faccio confronti!)
i += 1;
}
strcpy:
addi sp, sp, -8
sd s3, 0(sp)
add s3, zero, zero
L1:
add t0, s3, a1
lbu t1, 0(t0)
add t2, s3, a0
sb t1, 0(t2)
beq t1, zero, L2
addi s3, s3, 1
jal zero, L1
L2:
ld s3, 0(sp)
addi sp, sp, 8
jalr zero, 0(ra)
🏁 FINE LEZIONE 6