2008-11-03 15 views
6

Używając qsort w C przechodzimy w funkcji porównania np.Wskaźniki funkcji w C - operator adresu "niepotrzebny"

int cmp(const void*, const void*); 

protoype z qsort oczekuje int (*) (const void *, const void *) tak nazywamy:

qsort(..., cmp); 

ale jest równie ważne, aby zadzwonić:

qsort(..., &cmp); 

i to jest to, co musielibyśmy zrobić, gdybyśmy przekazali statyczną funkcję-członka w C++. Kernighan & Ritchie (2. wydanie, 5.11 "Wskaźniki do funkcji" str. 111) stwierdza, że ​​"ponieważ [cmp] jest znana jako funkcja, operator & nie jest potrzebny, tak jak nie jest potrzebny przed nazwą tablicy. "

Czy ktoś inny czuje się z tym trochę niekomfortowo (szczególnie pod względem bezpieczeństwa typu)?

Odpowiedz

8

To, czy czujesz się nieswojo, czy nie, nie zmienia faktu, że C nie jest uważany za język bezpieczny dla danego typu. Sprawa w punkcie:

int main() 
{ 
    int integer = 0xFFFFFF; 
    void (*functionPointer)() = (void(*)())integer; 

    functionPointer(); 

    return 0; 
} 

To jest całkowicie poprawny w czasie kompilacji, ale to oczywiście nie jest bezpieczne.

+3

To prawda, ale użycie tej obsady jest pałką, która mówi kompilatorowi "zaufaj mi, wiem, co robię, nawet jeśli myślisz inaczej". –

3

To szczegół składni. Jeśli nie lubisz nie używać wskaźnika &, aby wskazać wskaźnik funkcji, zawsze używaj go. Jeśli chcesz zapisać dodatkowe naciśnięcia klawiszy i nie być tak wyraźnym w swoim kodowaniu, skorzystaj z niego.

Jeśli chodzi o bezpieczeństwo typu, nie widzę, jak to robi. Kompilator otrzymuje funkcję jako argument i może sprawdzić, jaka funkcja jest podpisana całkowicie od nazwy. Fakt, że go nie wywołujesz (ze składnią()() (składnia), podaje wszystkie wskazania, których potrzebuje kompilator, aby wskaźnik funkcji znajdował się w tej lokalizacji. The & jest po prostu syntaktyczną delikatnością, która wskazuje ludziom, że patrzą na wskaźnik jakiegoś opisu.

Jeśli korzystasz z C++, to sugerowałbym, aby sprawdzić funktory doładowania jako ładniejszą metodę przekazywania funkcji. Te urocze byty pozwalają na jednolitą i dość przejrzystą składnię dla wszystkich funkcji w C++ :)

1

To nie tyle poczucie dyskomfortu, ile niechęć do poprawnych, ale sprzecznych opcji składni. Albo cmp jest wskaźnikiem i powinno być traktowane konsekwentnie jako takie, albo jest to inny typ - który IMHO jest syntaktycznie mylący.

Idąc nieco dalej, ja też wymagać połączenia zarówno do wyłuskiwania wskaźnika (zwrócić uwagę na fakt, że jest to wskaźnik), czy nie (bo nazwa funkcji jest wskaźnik) ... ale nie pozwalają obie.

Wskaźniki funkcji mogą być niezwykle wydajne, ale wydają się być źródłem nieporozumień zarówno dla nowych, jak i doświadczonych programistów. Część trudności może zostać złagodzona przez wymaganie pojedynczej, znaczącej składni, która wyjaśniałaby ich użycie, a nie w tym przypadku.

4

Podejrzewam, że czujesz się nieswojo, ponieważ nie jesteś przyzwyczajony do kodowania jako "blisko metalu", jak pozwala na to C. Powodem, dla którego C nie wymaga operatora adresu na funkcjach, jest to, że nie można naprawdę zrobić z funkcjami innymi niż wywoływanie ich lub przekazywanie.

Innymi słowy, nie ma rozsądnej definicji manipulowania "wartością" funkcji.

Podczas gdy z ints można dodawać lub odejmować od wartości, nie ma to sensu dla funkcji.

W każdym razie C poprzedza zarówno C++, jak i własną standaryzację - oryginalne K & R C nie miało nawet prototypów, a ANSI musiała upewnić się, że ich standaryzacja nie załamała istniejącego kodu tak bardzo jak to możliwe.

+1

na pewno. Możesz zrobić functionptr ++ i robić złe, złe rzeczy na stosie. ;) –

1

Należy zauważyć, że to samo dotyczy wskaźników funkcji "dereferencyjnych" (tj. Wywołujących); nie masz do

(*funcptr)(arg1, arg2); 

jak

funcptr(arg1, arg2); 

wystarczy.

5

Cóż, odpowiedź polega na tym, że przekazanie funkcji przez wartość daje wskaźnik funkcji, tak jak przekazanie tablicy przez wartość daje wskaźnik do pierwszego elementu. jeden mówi, że tablica i funkcja "rozpadu". jest tylko kilka okazji, kiedy to zepsucie się nie zdarza. na przykład sizeof (tablica) daje rozmiar tablicy, a nie wskaźnik pierwszego elementu. sizeof (funkcja) jest nieprawidłowa (funkcje nie są obiektami), musisz zrobić sizeof (funkcja &). Inne sytuacje są wiążące do referencji:

void baz(); 

void foo(void (&bar)()) { 
    bar(); 
} 

// doesnt work, since a reference to a function is requested. 
// you have to pass 'bar' itself, without taking its address 
// explicitely. 
foo(&baz); 

To btw wszystko jest powodem można zrobić

template<typename T, int N> 
void ByRef(T (&foo)[N]) { 
    ... 
} 

ponieważ tablica nie ma jeszcze rozkładowi przy rozważaniu parametry referencyjne.