C czy to nie takie trudne: (((([]) ()) ()

Właśnie widziałem dziś zdjęcie i myślę, że będę wdzięczny za wyjaśnienia. Oto zdjęcie:

jakiś kod c

Uznałem to za mylące i zastanawiałem się, czy takie kody są kiedykolwiek praktyczne. Wygooglowałem zdjęcie i znalazłem kolejne zdjęcie w tym wpisie Reddita, a tu jest to zdjęcie:

ciekawe Wyjaśnienie

Więc to "czytanie spiralnie" jest czymś ważnym? Czy tak kompilatory C analizują?
Byłoby świetnie, gdyby były prostsze wyjaśnienie tego dziwnego kodu.
Czy takie kody mogą być przydatne? Jeśli tak, to gdzie i kiedy?

Jest pytanie o "regułę spiralną", ale nie pytam tylko o to, jak jest stosowana lub jak wyrażenia są odczytywane z tą regułą. Kwestionuję użycie takich wyrażeń i zasadność spiral rule. Jeśli chodzi o te, kilka miłych odpowiedzi jest już zamieszczonych.

Author: Motun, 2015-12-31

13 answers

Istnieje reguła zwana "zgodnie z ruchem wskazówek zegara / spirali" , aby pomóc znaleźć znaczenie złożonej deklaracji.

From C-faq :

Istnieją trzy proste kroki do wykonania:

  1. Zaczynając od nieznanego elementu, poruszaj się w kierunku spiralnym / zgodnie z ruchem wskazówek zegara; podczas ecounteringu następujące elementy zastępują je odpowiednimi angielskimi wypowiedziami:

    [X] lub []
    = > Array x size of... or Array nieokreślony rozmiar...

    (type1, type2)
    = > funkcja przekazująca type1 i type2 zwracająca...

    *
    = > wskaźnik(y) do...

  2. Rób to spiralnie / zgodnie z ruchem wskazówek zegara, aż wszystkie żetony zostaną zakryte.

  3. Zawsze najpierw rozwiąż wszystko w nawiasie!

Możesz sprawdzić powyższy link dla przykładów.

Należy również pamiętać, że aby pomóc istnieje również strona internetowa nazwa:

Http://www.cdecl.org

Możesz wprowadzić deklarację C, która da jej angielskie znaczenie. Dla

void (*(*f[])())()

Wychodzi:

Declare f as array of pointer to function returning pointer to function returning void

EDIT:

Jak wspomniano w komentarzach przez Random832, reguła spirala nie adres tablicy tablic i doprowadzi do błędnego wyniku w (większości) tych deklaracje. Na przykład dla int **x[1][2]; reguła spirali ignoruje fakt, że [] ma wyższy priorytet nad *.

Przed tablicą tablic można najpierw dodać jawne nawiasy przed zastosowaniem reguły spiralnej. Na przykład: int **x[1][2]; jest tym samym co int **(x[1][2]); (również poprawne C) ze względu na pierwszeństwo, A reguła spirala poprawnie odczytuje ją jako "x jest tablicą 1 tablicy 2 wskaźnika do wskaźnika do int", która jest poprawną angielską deklaracją.

Zauważ, że ten problem ma również w odpowiedzi napisał James Kanze (wskazał haccks w komentarzach).

 116
Author: ouah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:30:45

Zasada "spirali" wypada z następujących zasad pierwszeństwa:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

Operatory indeksu dolnego [] i wywołania funkcji () mają wyższy priorytet niż jednoargumentowe *, więc *f() jest przetwarzane jako *(f()), a *a[] jako *(a[]).

Więc jeśli chcesz wskaźnik do tablicy lub wskaźnik do funkcji, musisz jawnie pogrupować * z identyfikatorem, jak w (*a)[] lub (*f)().

Wtedy zdajesz sobie sprawę, że a i f mogą być bardziej skomplikowane wyrażenia niż tylko identyfikatory; w T (*a)[N], a Może to być prosty identyfikator, albo może to być wywołanie funkcji jak (*f())[N] (a -> f()), albo może to być tablica jak (*p[M])[N], (a -> p[M]), lub może to być tablica wskaźników do funkcji takich jak (*(*p[M])())[N] (a -> (*p[M])()), itd.

Byłoby miło, gdyby operator indrection * był postfixem zamiast unary, co sprawiłoby, że deklaracje byłyby nieco łatwiejsze do odczytania od lewej do prawej (void f[]*()*(); zdecydowanie płynie lepiej niż void (*(*f[])())()), ale tak nie jest.

Kiedy natkniesz się na taką deklarację, zacznij od znalezienia identyfikatora Z Lewej strony i zastosuj powyższe reguły pierwszeństwa, rekurencyjnie stosując je do dowolnych parametrów funkcji:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

Funkcja signal w bibliotece standardowej jest prawdopodobnie wzorcem typu dla tego rodzaju szaleństwa:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void
W tym momencie większość ludzi mówi "użyj typedefów", co z pewnością jest opcją:]}
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];
Ale...

Jak czy mógłbyś użyć f w mowie? Wiesz, że to tablica wskaźników, ale jak jej użyć do wykonania prawidłowej funkcji? Musisz przejść przez typedefs i puzzle poprawną składnię. W przeciwieństwie do tego, Wersja" Naga " jest dość oszałamiająca, ale mówi dokładnie, jak {45]} używać f w wyrażeniu (mianowicie (*(*f[i])())();, zakładając, że żadna z funkcji nie przyjmuje argumentów).

 107
Author: John Bode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 18:30:29

W C, deklaracja odzwierciedla użycie-tak jest zdefiniowana w standardzie. "Deklaracja": {]}

void (*(*f[])())()

Jest twierdzeniem, że wyrażenie (*(*f[i])())() daje wynik typu void. Co oznacza:

  • f musi być tablicą, ponieważ możesz ją indeksować:

    f[i]
    
  • Elementy f muszą być wskaźnikami, ponieważ można je dereferować:

    *f[i]
    
  • Te wskaźniki muszą być wskaźnikami do funkcji nie pobierających argumentów, ponieważ można zadzwoń do nich:

    (*f[i])()
    
  • Wyniki tych funkcji muszą być również wskaźnikami, ponieważ można je dereferować:

    *(*f[i])()
    
  • Te wskaźniki muszą również być wskaźnikami do funkcji nie pobierających argumentów, ponieważ można je wywołać:

    (*(*f[i])())()
    
  • Te wskaźniki funkcji muszą zwracać void

"reguła spirali" jest po prostu mnemoniką, która zapewnia inny sposób zrozumienia tej samej rzeczy.

 58
Author: Jon Purdy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-01 01:27:40

Więc to "czytanie spiralnie" jest czymś ważnym?

Stosowanie reguły spiralnej lub używanie cdecl Nie zawsze jest poprawne. W niektórych przypadkach oba zawodzą. Reguła Spiral działa w wielu przypadkach, ale nie jest uniwersalna.

Aby rozszyfrować złożone deklaracje, Zapamiętaj te dwie proste zasady:

  • Zawsze czytaj deklaracje od wewnątrz : zacznij od najbardziej wewnętrznej, jeśli w ogóle, nawiasu. Zlokalizuj identyfikator, który jest zadeklarowane i zacznij odszyfrowywać deklarację stamtąd.

  • Gdy jest wybór, zawsze faworyzuj [] i () NAD *: Jeśli * poprzedza identyfikator i [] podąża za nim, identyfikator reprezentuje tablicę, a nie wskaźnik. Podobnie, Jeśli * poprzedza identyfikator i () podąża za nim, identyfikator reprezentuje funkcję, a nie wskaźnik. (Nawiasy zawsze mogą być użyte do nadpisania normalnego priorytetu [] i () NAD *.)

Zasada ta w rzeczywistości obejmuje zygzakowanie z jednej strony identyfikatora na drugą.

Teraz rozszyfrowanie prostej deklaracji

int *a[10];

Zasada stosowania:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Rozszyfrujmy złożoną deklarację jak

void ( *(*f[]) () ) ();  

Stosując powyższe zasady:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Oto GIF pokazujący, jak się idzie (kliknij na obrazek, aby powiększyć widok):

Tutaj wpisz opis obrazka


Zasady wspomniane tutaj jest zaczerpnięte z książki C Programming a Modern Approach autorstwa K. N Kinga.

 32
Author: haccks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:35

Jest to tylko "spirala", ponieważ w tej deklaracji jest tylko jeden operator z każdej strony w każdym poziomie nawiasów. Twierdzenie, że postępujesz "w spirali" sugerowałoby zmianę tablic i wskaźników w deklaracji int ***foo[][][], gdy w rzeczywistości wszystkie poziomy tablic są przed któremkolwiek z poziomów wskaźników.

 12
Author: Random832,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-01 01:46:47

Wątpię, by takie konstrukcje miały jakikolwiek sens w prawdziwym życiu. Nie znoszę ich nawet jako pytań wywiadowych dla zwykłych programistów (prawdopodobnie OK dla autorów kompilatorów). zamiast tego należy stosować typedefs.

 7
Author: SergeyA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 16:11:13

Jako przypadkowa ciekawostka, może być zabawne wiedzieć, że istnieje rzeczywiste słowo w języku angielskim, aby opisać, jak deklaracje C są odczytywane: Boustrophedonically, czyli naprzemiennie prawo-do-lewej z lewo-do-prawej.

Indeks: Van der Linden, 1994 - strona 76

 7
Author: asamarin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-01 20:59:37

Jeśli chodzi o użyteczność tego, podczas pracy z shellcode często widzisz ten konstrukt:

int (*ret)() = (int(*)())code;
ret();

Chociaż nie jest to tak skomplikowane składniowo, ten konkretny wzór pojawia się często.

Bardziej kompletny przykład w to więc pytanie.

Tak więc, podczas gdy użyteczność w zakresie w oryginalnym obrazie jest wątpliwa (sugerowałbym, że każdy kod produkcyjny powinien być drastycznie uproszczony), istnieją pewne konstrukty składniowe, które pojawiają się dość trochę.

 5
Author: Casey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 16:59:24

Deklaracja

void (*(*f[])())()

Jest tylko niejasnym sposobem powiedzenia

Function f[]

Z

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

W praktyce zamiast ResultFunction i Function będą potrzebne bardziej opisowe nazwy. Jeśli to możliwe, chciałbym również określić listę parametrów jako void.

 5
Author: August Karlstrom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-11 18:08:29

Według mnie metoda opisana przez Bruce ' a Eckela jest pomocna i łatwa do naśladowania:

Definiowanie wskaźnika funkcji

Aby zdefiniować wskaźnik do funkcji, która nie ma argumentów i nie zwraca wartość, mówisz:

void (*funcPtr)();

Gdy patrzymy na złożoną definicję jak to, najlepszym sposobem, aby go zaatakować, jest rozpoczęcie w środku i praca twoje wyjście. "Starting in the middle" oznacza rozpoczęcie od zmiennej nazwa, która jest functr. "Working your way out" czyli patrząc na prawo do najbliższego elementu (nic w tym przypadku; prawo nawias zatrzymuje cię na krótko), a następnie patrząc w lewo (wskaźnik oznaczony gwiazdką), następnie patrząc w prawo (pusty argument lista wskazująca funkcję, która nie pobiera argumentów), następnie patrząc na lewa (void, co oznacza, że funkcja nie ma zwracanej wartości). Ten ruch prawo-lewo-prawo działa z większością deklaracji.

Do recenzji: "start in the middle " ("functr is a ..."), przejdź w prawo (nic tam nie ma – zatrzymuje cię prawy nawias), przejdź do w lewo i znaleźć'*' ("... wskaźnik do A..."), przejdź w prawo i znajdź pustą listę argumentów ("... funkcja, która nie przyjmuje argumentów ... "), przejdź w lewo i znajdź void ("functr jest wskaźnikiem do funkcja, która nie przyjmuje argumentów i zwraca void").

Możesz się zastanawiać, dlaczego * functr wymaga nawiasów. Jeśli nie użyłeś ich, kompilator by Zobacz:

void *funcPtr();

Można by zadeklarować funkcję (która zwraca void*) zamiast definiowania zmiennej. Możesz pomyśleć o kompilatorze jak przechodząc przez ten sam proces, który robisz, gdy dowiadujesz się, co deklaracja lub definicja ma być. Potrzebuje tych nawiasy do "bump up against" więc wraca do lewej i znajduje "*", zamiast iść w prawo i znaleźć puste lista argumentów.

Skomplikowane deklaracje & definicje

Na marginesie, gdy dowiesz się jak składnia deklaracji C i C++ prace można tworzyć znacznie bardziej skomplikowane elementy. Na przykład:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Przejdź przez każdy z nich i użyj prawo-lewo wytyczne, aby to rozgryźć. liczba 1 mówi " fp1 jest wskaźnikiem do funkcja, która pobiera argument integer i zwraca wskaźnik do tablica 10 pustych wskaźników."

Liczba 2 mówi: "fp2 jest wskaźnikiem do funkcji, która przyjmuje trzy argumenty (int, int i float) i zwraca wskaźnik do funkcji pobiera argument integer i zwraca zmiennoprzecinkowy."

Jeśli tworzysz wiele skomplikowanych definicji, możesz chcieć aby użyć typedef. Liczba 3 pokazuje jak typedef zapisuje wpisywanie skomplikowany Opis za każdym razem. "3PR jest wskaźnikiem do funkcja, która nie pobiera argumentów i zwraca wskaźnik do tablicy 10 wskaźników do funkcji, które nie pobierają argumentów i zwracają podwójne." Następnie pisze: "a jest jednym z tych typów fp3."typedef jest ogólnie przydatne do budowania skomplikowanych opisów z prostych.

Liczba 4 jest deklaracją funkcji zamiast definicji zmiennej. Mówi " f4 jest funkcją, która zwraca wskaźnik do tablicy 10 wskaźniki do funkcji zwracających liczby całkowite."

Rzadko kiedy będziesz potrzebował tak skomplikowanych deklaracji i definicje jako te. Jednak, jeśli przejść przez ćwiczenia zastanawiając się nad nimi, nie będziesz nawet lekko zaniepokojony nieco skomplikowane, które można spotkać w prawdziwym życiu.

Zaczerpnięte z: Thinking in C++ Volume 1, second edition, chapter 3, section" Function Addresses " Bruce Eckel.

 4
Author: user3496846,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-03 01:29:31

Zapamiętaj te zasady dla C
I nigdy nie będzie wątpliwości:
Zacznij od przyrostka, kontynuuj z przedrostkiem,
I odczytać oba zestawy od wewnątrz, Na Zewnątrz.
-- ja, połowa lat 80-tych

Z wyjątkiem zmodyfikowanych nawiasami, oczywiście. I zauważ, że składnia do deklarowania tych wartości dokładnie odzwierciedla składnię do używania tej zmiennej do uzyskania instancji klasy bazowej.

[[2]} poważnie, to nie jest trudne do nauczenia się na pierwszy rzut oka; po prostu trzeba poświęcić trochę czasu na ćwiczenie umiejętności. Jeśli masz zamiar utrzymać lub dostosować kod C napisany przez innych ludzi, to zdecydowanie Warto zainwestować ten czas. Jest to również zabawna sztuczka imprezowa dla przerażających innych programistów, którzy nie nauczyli się tego.

Dla własnego kodu: jak zawsze, fakt, że coś Może być napisane jako jednolinijkowy, nie oznacza, że powinno być, chyba że jest to niezwykle powszechny wzorzec, który stał się standardowym idiomem (np. string-copy loop). Ty i ci, którzy za tobą podążają, będziecie znacznie szczęśliwsi, jeśli zbudujecie złożone typy z warstwowych typów i dereferencji krok po kroku, zamiast polegać na swojej zdolności do generowania i analizowania tych "w jednym puchnącym stopie."Wydajność będzie równie dobra, a czytelność i konserwacja kodu będą znacznie lepsze.

Mogło być gorzej. Było legalne oświadczenie PL / I, które zaczęło się od czegoś w stylu:
if if if = then then then = else else else = if then ...
 4
Author: keshlam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-03 09:17:08

Tak się składa, że jestem oryginalnym autorem Zasady spirali, którą napisałem oh tak wiele lat temu (kiedy miałem dużo włosów:) i byłem zaszczycony, gdy został dodany do cfaq.

Napisałem regułę spiral jako sposób, aby ułatwić moim uczniom i kolegom czytanie deklaracji C "w ich głowie", tzn. bez konieczności używania narzędzi programowych, takich jak cdecl.org, itd. Nigdy nie miałem zamiaru deklarować, że reguła spirala jest kanonicznym sposobem parsowania wyrażeń C. Jestem zachwycona aby zobaczyć, że zasada pomogła dosłownie tysiącom studentów i praktyków programowania C przez lata!

Dla przypomnienia,

Wielokrotnie było" poprawnie " identyfikowane na wielu stronach, w tym przez Linusa Torvaldsa (kogoś kogo ogromnie szanuję), że są sytuacje w których moja spiralna zasada "łamie się". Najczęściej:

char *ar[10][10];

Jak zauważyli inni w tym wątku, reguła może być zaktualizowana, aby powiedzieć, że gdy napotkasz tablice, po prostu wszystkie indeksy tak jakby napisano TAK:

char *(ar[10][10]);

Teraz, zgodnie z zasadą spirali, otrzymałbym:

"ar jest dwuwymiarową tablicą wskaźników do znaku 10x10"

Mam nadzieję, że reguła spiral kontynuuje swoją przydatność w nauce C!

P. S.:

Podoba mi się obraz "C nie jest trudne":) [3]}
 4
Author: David Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-23 01:51:39
  • void (*(*f[]) ()) ()

void >>

  • (*(*f[]) ()) () = void

Resoiving () >>

  • (*(*f[]) ()) = funkcja Return (void)

* >>

  • (*f[]) () = wskaźnik do (funkcja zwracająca (void))

() >>

  • (*f[]) = funkcja zwracająca (wskaźnik do (funkcja zwracająca (void)))

Rozwiązywanie * >>

  • f [] = Wskaźnik do (funkcja zwracająca (wskaźnik do (funkcja zwracająca (void))))

[ ] >>

  • f = array of (pointer to (function returning (pointer to (function Return (void) ))))
 3
Author: Shubham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-25 06:28:49