Lo standard C (e C++) non definisce l'ordine degli argomenti che vengono passati o il modo in cui devono essere organizzati in memoria. Spetta allo sviluppatore del compilatore (di solito in collaborazione con gli sviluppatori del sistema operativo) creare qualcosa che funzioni su una particolare architettura del processore.
Nelle architetture MOST, lo stack (ei registri) viene utilizzato per passare argomenti a una funzione, e ancora, per le architetture MOST, lo stack cresce da indirizzi "alto a basso" e nella maggior parte delle implementazioni C, l'ordine di essi vengono passati sono "di sinistra ultimo", quindi se abbiamo una funzione
void test(int a, int b, int c)
poi argomenti sono passati nell'ordine:
c, b, a
alla funzione.
Tuttavia, ciò che complica è quando il valore degli argomenti viene passato nei registri e il codice che utilizza gli argomenti sta prendendo l'indirizzo di quegli argomenti - i registri non hanno indirizzi, quindi non puoi prendere l'indirizzo di una variabile di registro. Quindi il compilatore genererà del codice per memorizzare l'indirizzo nello stack [da dove possiamo ottenere l'indirizzo del valore] localmente alla funzione. Questo dipende interamente dalla decisione del compilatore che ordina di farlo e sono abbastanza sicuro che questo è ciò che stai vedendo.
Se vi prendete il vostro codice e farlo passare attraverso clang, vediamo:
define void @test(i32 %a, i32 %b, i32 %c) #0 {
entry:
%a.addr = alloca i32, align 4
%b.addr = alloca i32, align 4
%c.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
store i32 %b, i32* %b.addr, align 4
store i32 %c, i32* %c.addr, align 4
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str, i32 0, i32 0), i32* %a.addr, i32* %b.addr, i32* %c.addr)
%add.ptr = getelementptr inbounds i32, i32* %b.addr, i64 -1
%0 = load i32, i32* %add.ptr, align 4
%add.ptr1 = getelementptr inbounds i32, i32* %b.addr, i64 1
%1 = load i32, i32* %add.ptr1, align 4
%call2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.1, i32 0, i32 0), i32 %0, i32 %1)
ret void
}
Anche se non può essere del tutto banale per leggere, si possono vedere le prime righe del test-funzione è:
%a.addr = alloca i32, align 4
%b.addr = alloca i32, align 4
%c.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
store i32 %b, i32* %b.addr, align 4
store i32 %c, i32* %c.addr, align 4
Questo sta creando essenzialmente spazio sulla pila (%alloca
) e memorizzare le variabili a
, b
e c
in quelle posizioni.
Ancora meno facile da leggere è il codice assembler che gcc genera, ma si può vedere una cosa simile accade qui:
subq $16, %rsp ; <-- "alloca" for 4 integers.
movl %edi, -4(%rbp) ; Store a, b and c.
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
leaq -12(%rbp), %rcx ; Take address of ...
leaq -8(%rbp), %rdx
leaq -4(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf ; Call printf.
si potrebbe chiedere perché si alloca spazio per 4 interi - questo è perché lo stack dovrebbe essere sempre allineati a 16 byte in x86-64.
possibile duplicato di [Compilatori e ordine di argomentazione di valutazione in C++] (http: // stackoverflow.it/questions/621542/compilers-and-argument-order-of-evaluation-in-c) – Steephen
@Steephen Ordine di valutazione, davvero? – kravemir
@Miro è specifico dell'implementazione e l'ordine degli argomenti viene elaborato dal compilatore – Steephen