2013-04-24 30 views
9

Wielokrotnie wpadam na problem z ustawianiem procedur obsługi sygnałów w kodzie GTK +, nie wymagającym kilku parametrów i mając pokusę użycia tej samej funkcji co obsługa dla kilku sygnałów, których programy obsługi mają różne sygnatury - ale z pierwszym N argumenty (te, na których mi zależy) to samo.Czy można bezpiecznie wywoływać funkcję C z większą liczbą argumentów, niż się spodziewa?

Czy jest bezpieczny (w tym sensie, że nie jest niezdefiniowanym zachowaniem, a nie bardziej pragmatycznym określeniem "działa na moim komputerze?"), Aby przekazywać wskaźniki do funkcji GObject API, gdy te funkcje wymagają mniejszej liczby argumenty, niż faktycznie otrzymają od procesu emisji sygnału?

Czy to rozwód z GTK +, czy ten kod jest w porządku?

/* Note: No void *userdata argument! */ 
void show(int x) { 
    printf("x = %d\n", x); 
} 

void do_stuff(void (*fn)(int, void *), void *userdata) { 
    static int total = 0; 
    (*fn)(total, userdata); 
    total++; 
} 

void doitnow(void) { 
    do_stuff(&show, NULL); 
} 

Aby uzyskać dodatkowe informacje, należy omówić implikacje różnych typów wartości zwracanych między podpisem funkcji a stroną połączenia.

Edit: An almost-identical question sondy „kompatybilny typ funkcji” dokładniej i zwraca an answer directly addressing my concrete problem - że z łańcuchowym gobject obsługi sygnałów. TL; DR: Tak, jest to niezdefiniowane zachowanie, ale jest praktycznie idiomatyczne (choć nie obowiązkowe) w niektórych zestawach narzędzi.

+0

Jestem prawie pewien, że konwersja wskaźnika funkcji w ten sposób to UB. – Flexo

+3

@Flexo Konwersja wskaźnika funkcji jest OK. Używając go do czegokolwiek innego niż do konwersji, z drugiej strony, ... –

+0

BTW, nie ma potrzeby '(* fn)' w do_stuff. Po prostu wywołaj 'fn (total, userdata)'. Ponieważ 'fn' jest wskaźnikiem funkcji, operator wywołania funkcji' (total, userdata) 'robi dokładnie to, czego się spodziewałeś. – Jens

Odpowiedz

8

To wyraźnie niezdefiniowane zachowanie, za 6.5.2.2, pkt 9:

Jeśli funkcja jest zdefiniowana z typem, który nie jest zgodny z typem (wyrażenia) wskazywanego przez wyrażenie, które oznacza wywołana funkcja, zachowanie jest niezdefiniowane.

typu, który show jest zdefiniowana,

void show(int x) 

nie jest zgodny z typem ekspresji wskazywanego przez wskaźnik dzięki którym jest on nazywany,

void (*fn)(int, void *) 

także wyraźnie w 6.3.2.3, akapit 8:

Punkt er do funkcji jednego typu można przekonwertować na wskaźnik na funkcję innego typu iz powrotem; wynik powinien być równy oryginalnemu wskaźnikowi. Jeśli przekonwertowany wskaźnik jest używany do wywoływania funkcji, której typ nie jest zgodny z typem odniesienia, zachowanie jest niezdefiniowane.

Dla funkcji "typ zgodny" charakteryzuje się tym, 6.7.6.3 (15) [6.7.5.3 (15) C99]

do dwóch różnych funkcji, które będą zgodne, zarówno powinna określ kompatybilne typy zwrotu. Ponadto listy typów parametrów, jeżeli oba są obecne, uzgadniają liczbę parametrów i zastosowanie terminatora elipsy; odpowiednie parametry muszą mieć zgodne typy. Jeżeli jeden typ ma listę typów parametrów, a drugi typ jest określony przez deklarator funkcji, który nie jest częścią definicji funkcji i który zawiera pustą listę identyfikatorów, lista parametrów nie może zawierać terminatora elipsy, a typ każdego parametru powinien być kompatybilne z typem, który wynika z zastosowania domyślnych promocji argumentów.Jeżeli jeden typ ma listę typów parametrów, a drugi typ to określony przez definicję funkcji, która zawiera (ewentualnie pustą) listę identyfikatorów, oba muszą uzgodnić liczbę parametrów, a typ każdego parametru prototypu musi być zgodny z typ, który wynika z zastosowania domyślnych promocji argumentów do typu odpowiedniego identyfikatora. (Przy określaniu zgodności typu i typu złożonego każdy parametr zadeklarowany za pomocą funkcji lub typu tablicy przyjmuje się jako mający ustawiony typ, a każdy parametr zadeklarowany z kwalifikowanym typem przyjmuje się jako posiadający niekwalifikowaną wersję zadeklarowanego typu.)

Najbardziej bezpośrednio istotną częścią jest to, że liczba argumentów musi być taka sama.

+0

jak szybko to zrobiłeś? czy znasz standard na wyciągnięcie ręki? :-) –

+0

Nie tak dobrze, ale wiedziałem, gdzie szukać w tym przypadku. –

+0

Twoja odpowiedź pomogła mi znaleźć drugą połowę odpowiedzi - znaczenie "kompatybilnych typów" w odniesieniu do funkcji. Czy chcesz dodać to tutaj, zanim zaakceptuję to? Podobno jest to 6.7.5.3 §15. –

-1

czytaj http://www.unixwiz.net/techtips/win32-callconv-asm.html i http://www.csee.umbc.edu/~chang/cs313.s02/stack.shtml Wydaje się, że jeśli przekazujesz więcej, nie powinno to powodować problemu, ponieważ najpierw parametry są wypychane. Więc cokolwiek nie jest potrzebne, pozostaje w stosie wywołującym. Ale przeciwny sposób z pewnością spowoduje problem.

+0

Może się to zdarzyć w niektórych implementacjach, ale nie zmienia to, że jest niezdefiniowanym zachowaniem w stosunku do standardu. – Flexo

+0

Może to być OK, jeśli osoba wywołująca wyczyści stos, ale co z tym, kto wyszukał - czy nie pobierze niepoprawnych parametrów z powodu zbyt dużej ramki stosu parametrów? –

+0

Jeśli zamówienia są poprawne we właściwych parametrach, reszta parametrów leży w rzeczywistości poniżej tych parametrów w ramce stosu, poniżej adresu powrotu, poniżej wskaźnika bazowego. Tak więc, gdy czytający odczytuje parametry, czyta do tych, które powinien czytać, reszta leży po prostu nietknięta. Ale wśród tych, którzy mają czytać kalejda, jeśli coś jest nie tak, wtedy będzie problem. – abasu

2

jest to niezdefiniowane zachowanie w standardzie C, ale standard C również nakazuje obsługę funkcji z argumentami variadic, a jedynym sposobem, w jaki kompilatory implementują to drugie, jest zezwolenie na to pierwsze. Ta forma idiomatyczna jest obsługiwana przez każdy kompilator i platformę obsługiwaną przez GLIB i przez wiele lat - jeszcze przed stworzeniem GLib - jest bardzo mało prawdopodobne, że kiedykolwiek zostanie zerwana.

+0

Kontrapunkt: 20 lat temu użyłem MS QuickC, który można powiedzieć, aby użyć konwencji wywoływania "pascal" (odwrócenie kolejności argumentów na stosie - najpierw naciskając pierwszy argument, a nie ostatni). Osłabienie liczby argumentów błędnych w takim przypadku spowodowałoby awarię. Oczywiście zestaw narzędzi może dowolnie definiować te platformy, na których jest to problem, i wspierać tylko te, na których nie jest. –

+0

, więc powiedziałeś * bardzo * staremu kompilatorowi na platformie, która nie jest w żaden sposób wspierana przez GLib, na wiele lat przed tym, jak rozważano nawet GLIB, aby zrobić coś, co naśladuje inny język i rzeczy się zepsuły. to byłby problem ... jak? :-) – ebassi