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):

#include <stdio.h>

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:

                .text
                .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:

_inc:
        pushq       %rbp
        movq        %rsp, %rbp
        movl        %edi, -4(%rbp)
        addl        $1, -4(%rbp)
        popq        %rbp
        ret

Aufgabe:

Call by Reference Funktionalität in C

Call by Reference Funktionalität erzielt man in C durch Pointer:

#include <stdio.h>

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:

                .text
                .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

                .text
                .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:

Beispiel in Fortran

      SUBROUTINE INC( P )

      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              
                .text
                .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