2015-05-17 13 views
15

Wygląda na to, że kompilatory state-of-art traktują argumenty przekazywane przez stos jako tylko do odczytu. Zauważ, że w konwencji wywoływania x86, wywołujący wypycha argumenty na stos, a wywołujący używa argumentów w stosie. Na przykład, następujące kod C:Konwencja wywoływania x86: czy argumenty przekazywane przez stos powinny być tylko do odczytu?

extern int goo(int *x); 
int foo(int x, int y) { 
    goo(&x); 
    return x; 
} 

zbierane przez clang -O3 -c g.c -S -m32 Os X 10.10 na:

.section __TEXT,__text,regular,pure_instructions 
    .macosx_version_min 10, 10 
    .globl _foo 
    .align 4, 0x90 
_foo:         ## @foo 
## BB#0: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    movl 8(%ebp), %eax 
    movl %eax, -4(%ebp) 
    leal -4(%ebp), %eax 
    movl %eax, (%esp) 
    calll _goo 
    movl -4(%ebp), %eax 
    addl $8, %esp 
    popl %ebp 
    retl 


.subsections_via_symbols 

tu parametr x (8(%ebp)) najpierw ładowane do %eax; a następnie przechowywany w -4(%ebp); a adres -4(%ebp) jest przechowywany w %eax; i %eax jest przekazywana do funkcji goo.

Zastanawiam się dlaczego Clang generuje kod, który skopiować wartość przechowywaną w 8(%ebp) do -4(%ebp), zamiast po prostu przechodząc adres 8(%ebp) do funkcji goo. Pozwoli to zaoszczędzić operacje związane z pamięcią i zapewnić lepszą wydajność. Podobnie zaobserwowałem zachowanie w GCC (pod OS X). Aby być bardziej konkretny, zastanawiam się, dlaczego kompilatory nie generują:

.section __TEXT,__text,regular,pure_instructions 
    .macosx_version_min 10, 10 
    .globl _foo 
    .align 4, 0x90 
_foo:         ## @foo 
## BB#0: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    leal 8(%ebp), %eax 
    movl %eax, (%esp) 
    calll _goo 
    movl 8(%ebp), %eax 
    addl $8, %esp 
    popl %ebp 
    retl 


.subsections_via_symbols 

szukałem dokumentów czy konwencja x86 wywołanie wymaga przekazany argumenty mają być tylko do odczytu, ale nie mogłem znaleźć nic na ten temat. Czy ktoś ma jakąś myśl na ten temat?

+1

Masz tu dobry punkt! '8 (% ebp)' znajduje się w ramce stosu wywołującego, ale jest to przestrzeń, która została przydzielona specjalnie do przekazywania argumentów do 'foo'. Czy wywołujący użyje tej przestrzeni dla własnych celów * po * 'foo' zwróci, zamiast po prostu go zniszczyć, dopasowując wskaźnik stosu? Jeśli tak, konieczne jest skopiowanie wartości do ramki stosu 'foo'. Jeśli nie, może to być bezpieczne dla 'foo', aby" pożyczyć "przestrzeń w ramce stosu dzwoniącego zamiast kopiować. Aby wiedzieć, czy twój pomysł jest dobry, czy nie, musisz zobaczyć, jak wygląda kod dla 'foo's * caller *. –

+0

@AlexD Dziękujemy za komentarz! Ponieważ 'foo' może być wywołany przez dowolną funkcję, myślę, że jest to pytanie dotyczące konwencji wywoływania, a nie konkretnego kontekstu, w którym' foo' jest wywoływany. –

+1

To interesujące pytanie. Znalazłem [to inne pytanie] (http://stackoverflow.com/questions/1684682/c-calling-conventions-and-passed-arguments?rq=1), które twierdzi, że gcc -O2 faktycznie zmodyfikował argument stosu wywoływanego przez wywołującego . – JS1

Odpowiedz

6

Faktycznie, ja po prostu skompilowany przy użyciu tej funkcji GCC:

int foo(int x) 
{ 
    goo(&x); 
    return x; 
} 

I to generowany ten kod:

_foo: 
     pushl  %ebp 
     movl  %esp, %ebp 
     subl  $24, %esp 
     leal  8(%ebp), %eax 
     movl  %eax, (%esp) 
     call  _goo 
     movl  8(%ebp), %eax 
     leave 
     ret 

to używając GCC 4.9.2 (na 32-bitowym Cygwin jeśli to ma znaczenie), brak optymalizacji. Tak więc GCC zrobił dokładnie to, co myślałeś, że powinien zrobić i użył argumentu bezpośrednio z miejsca, w którym dzwoniący wepchnął go na stos.

5

Na C programming language zlecono przekazywanie argumentów by value. Zatem każda modyfikacja argumentu (jak x++; jako pierwsza instrukcja twojego foo) jest lokalna dla funkcji i nie jest propagowana do wywołującego.

W związku z tym ogólna konwencja wywoływania powinna wymagać kopiowania argumentów w każdym miejscu wywołania. Konwencje wywoływania powinny być na tyle ogólne, aby wywoływać nieznane , np., np. przez wskaźnik funkcji!

Oczywiście, jeśli przekażesz adres do pewnej strefy pamięci, wywoływana funkcja może zwolnić wskaźnik, np. jak w

int goo(int *x) { 
    static int count; 
    *x = count++; 
    return count % 3; 
} 

BTW, można użyć optymalizacji łącza chwili (po kompilacji i łączenie z clang -flto -O2 lub gcc -flto -O2) może włączyć do kompilatora do ulepszania lub inline kilka połączeń pomiędzy jednostek tłumaczeniowych.

Należy zauważyć, że zarówno Clang/LLVM, jak i GCC są kompilatorami free software. Jeśli chcesz, możesz zaproponować im poprawkę (ale ponieważ oba są bardzo skomplikowanymi programami, będziesz potrzebował kilku miesięcy na zrobienie tej poprawki).

NB. Podczas sprawdzania wyprodukowanego kodu zespołu, prześlij -fverbose-asm do swojego kompilatora!

+0

Ponieważ przestrzeń dla argumentów jest alokowana przez zmniejszenie "% esp", spacja dla argumentów jest rozłączna względem ramki stosu wywołującego. Myślę więc, że modyfikowanie argumentów w stosie nie wpływa na ramkę stosu wywołującego. –

+0

Nie jestem pewien, czy podążać za twoim myśleniem (wygląda na to, że łamiesz wymóg "od wezwania do wartości"). Ale jeśli chcesz, możesz zaproponować łatkę dla [Clang/LLVM] (http://clang.llvm.org/) lub [GCC] (http://gcc.gnu.org/). To wymagałoby miesięcy pracy. –

+0

Powinieneś edytować swoje pytanie, aby je poprawić! –

12

Reguły dla C oznaczają, że parametry muszą być przekazywane wartością. Kompilator konwertuje z jednego języka (z jednym zestawem reguł) na inny język (potencjalnie z zupełnie innym zbiorem reguł). Jedynym ograniczeniem jest to, że zachowanie pozostaje takie samo.Reguły języka C nie odnoszą się do języka docelowego (na przykład do montażu).

Co oznacza, że ​​jeśli kompilator ma ochotę generować język asemblowy, w którym parametry są przekazywane przez odniesienie i nie są przekazywane przez wartość; to jest całkowicie legalne (o ile zachowanie pozostaje takie samo).

Prawdziwe ograniczenie nie ma nic wspólnego z C. Prawdziwym ograniczeniem jest łączenie. Aby różne pliki obiektów mogły być ze sobą połączone, potrzebne są standardy, aby zapewnić, że niezależnie od tego, który wywołujący w jednym pliku obiektowym oczekuje dopasowań, niezależnie od tego, który z nich znajduje się w pliku innego obiektu. Tak nazywa się ABI. W niektórych przypadkach (na przykład 64-bitowy 80x86) istnieje wiele różnych ABI dla tej samej architektury.

Możesz nawet wymyślić własną ABI, która jest radykalnie odmienna (i wdrożyć własne narzędzia, które wspierają twój radykalnie inny ABI) i jest to całkowicie legalne, jeśli chodzi o standardy C; nawet jeśli ABI wymaga "przekazania przez odniesienie" do wszystkiego (o ile zachowanie pozostaje takie samo).

Powiązane problemy