Znaczenie int (*) (int *) = 5 (lub dowolna liczba całkowita)

88

Nie mogę tego rozgryźć:

int main() {
    int (*) (int *) = 5;
    return 0;
}

Powyższe przypisanie kompiluje się z g ++ c ++ 11. Wiem, że int (*) (int *)jest to wskaźnik do funkcji, która przyjmuje (int *)argument jako argument i zwraca wartość int, ale nie rozumiem, jak można to zrównać do 5. Na początku myślałem, że jest to funkcja, która stale zwraca 5 (z mojej ostatniej nauki w F #, prawdopodobnie haha), wtedy pomyślałem krótko, że wskaźnik funkcji wskazuje na lokalizację pamięci 5, ale to oczywiście nie działa, podobnie jak wartości szesnastkowe.

Myśląc, że może to być spowodowane tym, że funkcja zwraca int, a przypisanie int jest w porządku (w jakiś sposób), spróbowałem też tego:

int * (*) (int *) = my_ptr

gdzie my_ptrjest typu int *, tego samego typu co ten drugi wskaźnik funkcji, tak jak w pierwszym przypadku typu int. To się nie kompiluje. Przypisanie 5 lub dowolnej wartości int zamiast tego my_ptrrównież nie jest kompilowane dla tego wskaźnika funkcji.

Więc co oznacza to zadanie?

Zaktualizuj 1

Mamy potwierdzenie, że jest to błąd, jak pokazano w najlepszej odpowiedzi. Jednak nadal nie wiadomo, co tak naprawdę dzieje się z wartością przypisaną do wskaźnika funkcji lub co dzieje się z przypisaniem. Wszelkie (dobre) wyjaśnienia na ten temat będą bardzo mile widziane! Zapoznaj się z poniższymi zmianami, aby uzyskać więcej informacji na temat problemu.

Edytuj 1

Używam gcc w wersji 4.8.2 (w Ubuntu 4.8.2)

Edytuj 2

Właściwie zrównanie tego z czymkolwiek działa na moim kompilatorze. Nawet zrównanie go ze zmienną std :: string lub nazwą funkcji, która zwraca wartość double, działa.

Edytuj 2.1

Co ciekawe, uczynienie go wskaźnikiem funkcji do dowolnej funkcji, która zwraca typ danych, który nie jest wskaźnikiem, pozwoli na kompilację, na przykład

std::string (*) () = 5.6;

Ale gdy tylko wskaźnik funkcji znajdzie się na funkcji, która zwraca jakiś wskaźnik, nie kompiluje się, na przykład with

some_data_type ** (*) () = any_value;
Konrad Kapp
źródło
3
Hmm ... to nie wygląda dobrze, a Clang tego nie akceptuje. Może to być rozszerzenie gcc (lub błąd).
Wintermute
4
g ++ kompiluje się, ale gcc nie działa:error: expected identifier or '(' before ')' token
tivn
3
@ 0x499602D Zauważ, że kod nie nadaje nazwy wskaźnikowi. Z int *x = 5tobą to nazwałeś x. Dzięki int * (*x) (int *) = 5temu nie będzie się kompilować. (aczkolwiek skompiluje się jako kod C).
nr
5
Zredukowany przypadek testowy: int(*) = 5;iint(*);
Johannes Schaub - litb
13
Wygląda na to, że gcc.gnu.org/bugzilla/show_bug.cgi?id=60680
TC

Odpowiedzi:

60

To błąd w g ++.

 int (*) (int *) 

to nazwa typu.

W C ++ nie można mieć deklaracji z nazwą typu bez identyfikatora.

Więc to kompiluje się z g ++.

 int (*) (int *) = 5;

i to również się kompiluje:

 int (*) (int *);

ale obie są nieważnymi deklaracjami.

EDYCJA :

TC wspomina w komentarzach o bugzilli o błędzie 60680 z podobnym przypadkiem testowym, ale nie został on jeszcze zatwierdzony . Błąd został potwierdzony w bugzilli.

EDYCJA2 :

Gdy dwie powyższe deklaracje znajdują się w zakresie plików, g ++ poprawnie wysyła diagnostykę (nie wydaje diagnostyki w zakresie blokowym).

EDYCJA3 :

Sprawdziłem i mogę odtworzyć problem w najnowszej wersji g ++ w wersji 4 (4.9.2), najnowszej wersji przedpremierowej 5 (5.0.1 20150412) i najnowszej wersji eksperymentalnej 6 (6.0.0 20150412).

ouah
źródło
5
Firma MSVC odrzuciła opublikowany kod zerror C2059: syntax error : ')'
Weather Vane
Jeśli jest to nazwa typu, dlaczego nie 'int (*) (int *) int_func;' praca?
Konrad Kapp
1
W przypadku bugzilli GCC „NOWY” jest potwierdzonym błędem. (Niepotwierdzone błędy są „NIEPOTWIERDZONE”).
TC
4
@KonradKapp: działa dobrze, jeśli powiesz, int (*int_func)(int *); która deklaruje wskaźnik funkcji o nazwie int_func.
Edward
3
@KonradKapp C ++ używa notacji wrostkowej do umieszczania identyfikatora; z tego samego powodu int x[5];i nieint[5] x;
MM
28

To nie jest poprawne C ++. Pamiętaj, że skoro Twój konkretny kompilator się kompiluje, nie oznacza to, że jest prawidłowy. Kompilatory, podobnie jak każde złożone oprogramowanie, czasami mają błędy, a ten wydaje się być jednym.

Natomiast clang++narzeka:

funnycast.cpp:3:11: error: expected expression
    int (*) (int *) = 5;
          ^
funnycast.cpp:3:18: error: expected '(' for function-style cast or type construction
    int (*) (int *) = 5;
             ~~~ ^
funnycast.cpp:3:19: error: expected expression
    int (*) (int *) = 5;
                  ^
3 errors generated.

Jest to oczekiwane zachowanie, ponieważ naruszający wiersz nie jest prawidłowy w C ++. To rzekomo przypisanie (z powodu =), ale nie zawiera identyfikatora.

Edward
źródło
9

Jak wskazywały inne odpowiedzi, jest to błąd

int (*) (int *) = 5;

kompiluje. Rozsądnym przybliżeniem tego stwierdzenia, które powinno mieć znaczenie, jest:

int (*proc)(int*) = (int (*)(int*))(5);

Teraz procjest wskaźnikiem do funkcji, która oczekuje adresu5 będzie adresem podstawowym funkcji, która przyjmuje int*i zwraca int.

W niektórych mikrokontrolerach / mikroprocesorach 5 może być prawidłowy adres kodowy i może być tam możliwe zlokalizowanie takiej funkcji.

Na większości komputerów ogólnego przeznaczenia pierwsza strona pamięci (adresy 0-1023 stron 4K) jest celowo nieważna (niezamapowana) w celu przechwytywania nulldostępu przez wskaźnik.

Tak więc, chociaż zachowanie zależy od platformy, można rozsądnie oczekiwać, że wystąpi błąd strony po *procwywołaniu (np.(*proc)(&v) .). Przed momentem *procwezwania nic niezwykłego się nie dzieje.

O ile nie piszesz dynamicznego linkera, prawie na pewno nie powinieneś obliczać liczbowo adresów i przypisywać ich do zmiennych wskaźnikowych do funkcji.

Atsby
źródło
2
/usr/lib/gcc/x86_64-pc-cygwin/4.9.2/cc1plus.exe -da so.cpp

Ta linia poleceń generuje wiele plików pośrednich. Pierwszy z nich so.cpp.170r.expandmówi:

...
int main() ()
{
  int D.2229;
  int _1;

;;   basic block 2, loop depth 0
;;    pred:       ENTRY
  _1 = 0;
;;    succ:       3

;;   basic block 3, loop depth 0
;;    pred:       2
<L0>:
  return _1;
;;    succ:       EXIT

}
...

To wciąż nie wyjaśnia, co się dokładnie dzieje, ale powinien to być krok we właściwym kierunku.

Roland Illig
źródło
Ciekawy. Jaki jest cel tych plików pośrednich?
Konrad Kapp
@KonradKapp Tworzenie kodu maszynowego z ludzkiego kodu jest dość złożonym procesem (szczególnie jeśli chcesz, aby Twój kompilator zoptymalizował swoje wyjście). Ponieważ kompilacja jest tak złożona, nie jest wykonywana w jednym kroku, większość kompilatorów ma jakąś formę reprezentacji pośredniej (IR).
11684
2
Innym powodem posiadania IR jest to, że jeśli masz dobrze zdefiniowaną IR, możesz oddzielić front-end i back-end kompilatora. (Np. Front-end kompiluje C do twojego IR, back-end kompiluje IR do kodu maszynowego Intela. Teraz, jeśli chcesz dodać obsługę ARM, potrzebujesz tylko drugiego zaplecza. A jeśli chcesz skompilować Go, potrzebujesz tylko drugi front-end, a do tego kompilator Go natychmiast obsługuje zarówno Intel, jak i ARM, ponieważ możesz ponownie użyć obu back-endów.
11684
@ 11684 OK, ma sens. Bardzo interesujące. Nie mogłem jednak określić, jakim językiem podał Roland w tej odpowiedzi ... wygląda na to, że jakieś zgromadzenie pomieszane z C.
Konrad Kapp
IR nie musi być drukowalne; Nie mam pojęcia, czego używa gcc, może to być tylko reprezentacja do druku @KonradKapp
11684