Call by Value, Call by Reference
In C werden an Funktionen die Paramter als Werte und nicht als Referenzen übergeben. Was damit gemeint ist lernen wir anhand von Beispielen.
Call by Value
Wir betrachten folgendes Programm und überlegen uns was es wohl machen sollte (das foo im Dateinamen deutet schon an, dass nicht alles so klappt wie es der Autor sich erhoffte):
void
inc(unsigned p)
{
p = p+1;
}
unsigned a = 4;
int
main()
{
printf("a before: %d\n", a);
inc(a);
printf("a after: %d\n", a);
return 0;
}
Wir übersetzen mutig und führen das Programm aus:
$shell> gcc-4.8 -Wall foo_inc.c $shell> ./a.out a before: 4 a after: 4
Der Wert wurde also nicht verändert. Oder doch? Aber wo? Wir schauen uns an was da genau passiert ist:
$shell> gcc-4.8 -S -fno-asynchronous-unwind-tables foo_inc.c
Es ist nämlich so:
.globl _inc
_inc:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
addl $1, -4(%rbp)
popq %rbp
ret
.globl _a
.data
.align 2
_a:
.long 4
.cstring
LC0:
.ascii "a before: %d\12\0"
LC1:
.ascii "a after: %d\12\0"
.text
.globl _main
_main:
pushq %rbp
movq %rsp, %rbp
movl _a(%rip), %eax
movl %eax, %esi
leaq LC0(%rip), %rdi
movl $0, %eax
call _printf
movl _a(%rip), %eax
movl %eax, %edi
call _inc
movl _a(%rip), %eax
movl %eax, %esi
leaq LC1(%rip), %rdi
movl $0, %eax
call _printf
movl $0, %eax
popq %rbp
ret
.subsections_via_symbols
Die Funktion inc erhält ihren Parameter über das Register %rdi:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
addl $1, -4(%rbp)
popq %rbp
ret
Aufgabe:
-
Was steht denn in %rdi (bzw. %edi da wir hier eine 32-bit Integer benutzen) wirklich drin? Schaut mal mit was das Register vorher gefüllt wurde.
-
Was wird dann mit %rdi in der inc Funktion getan? Das steht doch ein addl. Was wird denn addiert und was passiert mit dem Ergebnis?
-
Ändert den Assembler Code so um, dass inc den Wert von a erhöht (nicht aber direkt auf das Datensegment zugreifen. Es soll schon %edi benutzt werden).
-
Übersetzt euren Assembler Code und führt ihn aus!
Call by Reference Funktionalität in C
Call by Reference Funktionalität erzielt man in C durch Pointer:
void
inc(unsigned *p)
{
*p = *p+1;
}
unsigned a = 4;
int
main()
{
printf("a before: %d\n", a);
inc(&a);
printf("a after: %d\n", a);
return 0;
}
Was passiert hier?
$shell> gcc-4.8 -S -fno-asynchronous-unwind-tables inc.c
Der Assembler Code verrät uns, dass so unsere eigene Lösung von oben bequemer erzielt werden kann:
.globl _inc
_inc:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movl (%rax), %eax
leal 1(%rax), %edx
movq -8(%rbp), %rax
movl %edx, (%rax)
popq %rbp
ret
.globl _a
.data
.align 2
_a:
.long 4
.cstring
LC0:
.ascii "a before: %d\12\0"
LC1:
.ascii "a after: %d\12\0"
.text
.globl _main
_main:
pushq %rbp
movq %rsp, %rbp
movl _a(%rip), %eax
movl %eax, %esi
leaq LC0(%rip), %rdi
movl $0, %eax
call _printf
leaq _a(%rip), %rdi
call _inc
movl _a(%rip), %eax
movl %eax, %esi
leaq LC1(%rip), %rdi
movl $0, %eax
call _printf
movl $0, %eax
popq %rbp
ret
.subsections_via_symbols
Naja, noch deutlicher sieht man was jetzt passiert, wenn der Optimierer den ganzen unnötigen Kram weglässt
$shell> gcc-4.8 -S -O3 -fno-asynchronous-unwind-tables inc.c
Dann besteht die Funktion aus einer einzigen Assembler Anweisung
.align 4,0x90
.globl _inc
_inc:
addl $1, (%rdi)
ret
.cstring
LC0:
.ascii "a before: %d\12\0"
LC1:
.ascii "a after: %d\12\0"
.section __TEXT,__text_startup,regular,pure_instructions
.align 4
.globl _main
_main:
leaq LC0(%rip), %rdi
subq $8, %rsp
xorl %eax, %eax
movl _a(%rip), %esi
call _printf
movl _a(%rip), %eax
leaq LC1(%rip), %rdi
leal 1(%rax), %esi
xorl %eax, %eax
movl %esi, _a(%rip)
call _printf
xorl %eax, %eax
addq $8, %rsp
ret
.globl _a
.data
.align 4
_a:
.long 4
.subsections_via_symbols
Aufage:
-
Was ist mit unserem Funktionsaufruf geworden???
-
Wie nennt man diese Art von Optimierung?
-
Wie wird im Hauptprogramm die Eins addiert?
Beispiel in Fortran
INTEGER P
P = P + 1
END SUBROUTINE
PROGRAM MAIN
INTEGER A
A = 4
WRITE (*,*) 'A before:', A
CALL INC( A )
WRITE (*,*) 'A after:', A
END
Wer noch kein Fortran kennt soll mal an Hand des Assembler Code herausfinden was passiert:
$shell> gfortran -S -fno-asynchronous-unwind-tables f77_inc.f
.globl _inc_
_inc_:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movl (%rax), %eax
leal 1(%rax), %edx
movq -8(%rbp), %rax
movl %edx, (%rax)
popq %rbp
ret
.cstring
LC0:
.ascii "f77_inc.f\0"
.const
LC1:
.ascii "A before:"
LC2:
.ascii "A after:"
.text
_MAIN__:
pushq %rbp
movq %rsp, %rbp
subq $496, %rsp
movl $4, -4(%rbp)
leaq LC0(%rip), %rax
movq %rax, -488(%rbp)
movl $16, -480(%rbp)
movl $128, -496(%rbp)
movl $6, -492(%rbp)
leaq -496(%rbp), %rax
movq %rax, %rdi
call __gfortran_st_write
leaq -496(%rbp), %rax
movl $9, %edx
leaq LC1(%rip), %rsi
movq %rax, %rdi
call __gfortran_transfer_character_write
leaq -4(%rbp), %rcx
leaq -496(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call __gfortran_transfer_integer_write
leaq -496(%rbp), %rax
movq %rax, %rdi
call __gfortran_st_write_done
leaq -4(%rbp), %rax
movq %rax, %rdi
call _inc_
leaq LC0(%rip), %rax
movq %rax, -488(%rbp)
movl $18, -480(%rbp)
movl $128, -496(%rbp)
movl $6, -492(%rbp)
leaq -496(%rbp), %rax
movq %rax, %rdi
call __gfortran_st_write
leaq -496(%rbp), %rax
movl $8, %edx
leaq LC2(%rip), %rsi
movq %rax, %rdi
call __gfortran_transfer_character_write
leaq -4(%rbp), %rcx
leaq -496(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call __gfortran_transfer_integer_write
leaq -496(%rbp), %rax
movq %rax, %rdi
call __gfortran_st_write_done
leave
ret
.globl _main
_main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movq -16(%rbp), %rdx
movl -4(%rbp), %eax
movq %rdx, %rsi
movl %eax, %edi
call __gfortran_set_args
leaq _options.2.1890(%rip), %rsi
movl $7, %edi
call __gfortran_set_options
call _MAIN__
movl $0, %eax
leave
ret
.const
.align 4
_options.2.1890:
.long 68
.long 1023
.long 0
.long 0
.long 1
.long 1
.long 0
.subsections_via_symbols