Jak zrozumieć skomplikowane deklaracje funkcji?

Jak rozumieć przestrzeganie skomplikowanych deklaracji?

char (*(*f())[])();

char (*(*X[3])())[5];

void (*f)(int,void (*)()); 

char far *far *ptr;

typedef void (*pfun)(int,float);

int **(*f)(int**,int**(*)(int **,int **));
Author: Peter Mortensen, 2009-09-19

11 answers

Jak zauważyli inni, cdecl jest właściwym narzędziem do tego zadania.

Jeśli chcesz zrozumieć tego rodzaju deklarację bez pomocy cdecl, spróbuj czytać od wewnątrz i od prawej do lewej

Biorąc jeden losowy przykład z twojej listy char (*(*X[3])())[5];
Początek od X, który jest identyfikatorem deklarowanym/zdefiniowanym (i najbardziej wewnętrznym identyfikatorem):

char (*(*X[3])())[5];
         ^

X jest

X[3]
 ^^^

X jest tablica 3

(*X[3])
 ^                /* the parenthesis group the sub-expression */

X jest szeregiem 3 wskaźniki do

(*X[3])()
       ^^

X jest tablicą 3 wskaźników do funkcja przyjmująca nieokreśloną (ale stałą) liczbę argumentów

(*(*X[3])())
 ^                   /* more grouping parenthesis */

X jest tablicą 3 wskaźników do funkcji przyjmującej nieokreśloną (ale stałą) liczbę argumentów i zwracanie wskaźnika

(*(*X[3])())[5]
            ^^^

X jest tablicą 3 wskaźników do funkcji przyjmującą nieokreśloną (ale stałą) liczbę argumentów i zwracającą pointer do tablicy 5

char (*(*X[3])())[5];
^^^^                ^

X jest tablicą 3 wskaźników do funkcji przyjmującą nieokreśloną (ale stałą) liczbę argumentów i zwracającą wskaźnik do tablicy 5 char .

 71
Author: pmg,
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
2009-09-21 08:11:41

Odczytaj to od środka, podobnie jak rozwiązywałbyś równania, takie jak {3+5*[2+3*(x+6*2)]}=0 - zacząłbyś od rozwiązania tego, co jest w środku (), potem [] i wreszcie {}: {]}

char (*(*x())[])()
         ^

Oznacza to, że x jest czymś .

char (*(*x())[])()
          ^^

x jest funkcją .

char (*(*x())[])()
        ^

x zwraca wskaźnik do czegoś .

char (*(*x())[])()
       ^    ^^^

x zwraca wskaźnik do tablicy .

char (*(*x())[])()
      ^

x zwraca wskaźnik do tablicy wskaźniki .

char (*(*x())[])()
     ^         ^^^

x zwraca wskaźnik do tablicy wskaźników do funkcji

char (*(*x())[])()
^^^^

Co oznacza, że wskaźnik tablicy zwracany przez x wskazuje na tablicę wskaźników funkcji, która wskazuje na funkcje zwracające znak .

Ale tak, użyj cdecl . Sam użyłem go do sprawdzenia mojej odpowiedzi :).

Jeśli nadal cię to myli (i prawdopodobnie powinno), spróbuj zrobić to samo na kartce papieru lub w ulubionym tekście redaktor. Nie ma sposobu, aby dowiedzieć się, co to znaczy, tylko patrząc na to.

 16
Author: IVlad,
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
2010-04-18 23:57:54

Brzmi jak zadanie dla narzędzia cdecl:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

Rozejrzałem się po oficjalnej stronie głównej narzędzia, ale nie mogłem znaleźć takiej, która wydawałaby się prawdziwa. W Linuksie zazwyczaj można oczekiwać, że wybrana dystrybucja będzie zawierać narzędzie, więc po prostu zainstalowałem je w celu wygenerowania powyższej próbki.

 13
Author: unwind,
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
2009-09-19 17:18:25

Powinieneś używać narzędzia cdecl . Powinien być dostępny na większości dystrybucji Linuksa.

Np. dla tej funkcji zwróci Cię:

char (*(*f())[])(); - declare f as function returning pointer to array of pointer to function returning char

void (*f)(int,void (*)()); - prototyp wskaźnika funkcji f. f jest funkcją, która przyjmuje dwa parametry, pierwszy to int, a drugi to wskaźnik funkcji dla funkcji, która zwraca void.

char far *far *ptr; - ptr to a far pointer to a far pointer (który wskazuje na jakiś znak/bajt).

char (*(*X[3])())[5]; - X jest tablicą 3 wskaźników do funkcji przyjmującą nieokreśloną liczbę argumentów i zwracającą wskaźnik do tablicy 5 znaków.

typedef void (*pfun)(int,float); - deklarowanie wskaźnika funkcji pfun. pfun jest fuctnionem, który pobiera dwa parametry, pierwszy jest int, drugi jest typu float. funkcja nie ma zwracanej wartości;

Np.

void f1(int a, float b)
{ //do something with these numbers
};

Btw, skomplikowane deklaracje jako ostatnie nie są często spotykane. Oto przykład, który właśnie wymyśliłem.

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}
 5
Author: MannyNS,
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
2009-09-20 02:08:17

Wygląda na to, że twoje pytanie brzmi:]}

Jaki jest przypadek użycia wskaźnika do wskaźnika?

Wskaźnik do wskaźnika zwykle pojawia się, gdy masz tablicę typu T, a sam T jest wskaźnikiem do czegoś innego. Na przykład,

  • co to jest ciąg w C? Zazwyczaj jest to char *.
  • czy chciałbyś mieć od czasu do czasu tablicę strun? Jasne.
  • Jak byś je zadeklarował? char *x[10]: x jest array of 10 pointers to char, aka 10 strings.

W tym momencie, możesz się zastanawiać, gdzie char ** wchodzi. Wchodzi do obrazu z bardzo bliskiego związku między arytmetyką wskaźników i tablicami w C. nazwa tablicy x jest (prawie) zawsze konwertowana na wskaźnik do pierwszego elementu.

  • jaki jest pierwszy element? A char *.
  • co to jest wskaźnik do pierwszego elementu? A char **.

W C Tablica E1[E2] jest zdefiniowana jako równoważna do *(E1 + E2). Zazwyczaj E1 jest nazwą tablicy, powiedzmy x, która automatycznie przekształca się w char **, a {[15] } jest jakimś indeksem, powiedzmy 3. (Ta zasada wyjaśnia również, dlaczego 3[x] i x[3] są tym samym.)

Wskaźniki do wskaźników pojawiają się również, gdy chcesz dynamicznie przydzielaną tablicę pewnego typu T, która sama w sobie jest wskaźnikiem. Na początek udawajmy, że nie wiemy, co to jest typ T.

  • jeśli chcemy dynamicznie przydzielonego wektora T, jaki typ potrzebujemy? T *vec.
  • Dlaczego? Ponieważ możemy wykonywać arytmetykę wskaźnika w C, Dowolna T * może służyć jako baza ciągów T w pamięci.
  • Jak przydzielimy ten wektor, powiedzmy z n elementów? vec = malloc(n * sizeof(T));

Ta historia jest prawdziwa dla absolutnie każdego typu T, a więc jest prawdziwa dla char *.

  • jaki jest typ vec jeśli T to char *? char **vec.

Wskaźniki do wskaźników pojawiają się również, gdy masz funkcja, która musi zmodyfikować argument typu T, sam wskaźnik .

  • spójrz na deklarację dla strtol: long strtol(char *s, char **endp, int b).
  • O co chodzi? strtol konwertuje łańcuch z bazy b na liczbę całkowitą. Chce ci powiedzieć, jak daleko zaszło. Być może może zwrócić strukturę zawierającą zarówno a long, jak i a char *, ale nie tak jest deklarowana.
  • zamiast tego zwraca swój drugi wynik przekazując w adres ciągu znaków które modyfikuje przed powrotem.
  • co to jest sznurek? O tak, char *.
  • więc jaki jest adres łańcucha? char **.

Jeśli wędrujesz tą ścieżką wystarczająco długo, możesz również napotkać typy T ***, chociaż prawie zawsze możesz zrestrukturyzować kod, aby ich uniknąć.

Wreszcie, wskaźniki do wskaźników pojawiają się w pewnych skomplikowanych implementacjach połączonych list . Rozważmy standardową deklarację podwójnie połączonej listy w C.

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

To działa dobrze, chociaż nie będę odtwarzał funkcji wstawiania/usuwania tutaj, ale ma mały problem. Każdy węzeł może zostać usunięty z listy (lub wstawić przed nią nowy węzeł) bez odwoływania się do nagłówka listy. Nie do końca jakiś węzeł. Nie dotyczy to pierwszego elementu listy, gdzie {[39] } będzie null. Może to być umiarkowanie irytujące w niektórych rodzajach kodu C, gdzie pracujesz bardziej z samymi węzłami niż z listą jako koncepcja. Jest to dość powszechne zjawisko w systemach niskopoziomowych.

A jeśli przepiszemy node tak:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

W każdym węźle, prevp wskazuje nie na poprzedni węzeł, ale na wskaźnik next poprzednich węzłów. Co z pierwszym węzłem? prevp punkty na head. Jeśli narysujesz taką listę (i masz , aby ją narysować, aby zrozumieć, jak to działa) zobaczysz, że możesz usunąć pierwszy element lub wstawić nowy węzeł przed pierwszym element bez wyraźnego odwoływania się head Po nazwie.

 4
Author: Dale Hagglund,
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
2010-04-19 01:32:04

X: funkcja zwracająca wskaźnik do tablica[] wskaźnika do funkcji "returning char" - huh?

Masz funkcję

Ta funkcja zwraca wskaźnik.

Ten wskaźnik wskazuje na tablicę.

Tablica jest tablicą wskaźników funkcji (lub wskaźników do funkcji)

Funkcje te zwracają znak*.

 what's the use case for a pointer to a pointer?

Jednym z nich jest ułatwienie zwracania wartości za pomocą argumentów.

Powiedzmy, że masz

int function(int *p)
  *p = 123;
   return 0; //success !
}

Ty call it like

int x;
function(&x);

Jak widzisz, aby function móc zmodyfikować nasze x musimy przekazać mu wskaźnik do naszego x.

A gdyby x nie było int, ale char *? Cóż, to wciąż to samo, musimy podać na to wskazówkę. Wskaźnik do wskaźnika:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

You call it like

char *x;
function(&x); 
 2
Author: leeeroy,
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
2010-04-18 18:27:21

Remo.Odpowiedź D na funkcje czytania jest dobrą sugestią. Oto kilka odpowiedzi dla innych.

Jednym z przypadków użycia wskaźnika do wskaźnika jest przekazanie go do funkcji, która zmodyfikuje wskaźnik. Na przykład:

void foo(char **str, int len)
{
   *str = malloc(len);
}

Również może to być tablica ciągów:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

Zazwyczaj nie powinno używać deklaracji tak skomplikowanych, chociaż czasami potrzebne są typy, które są dość skomplikowane dla rzeczy takich jak Wskaźniki funkcji. W w takich przypadkach o wiele bardziej czytelne jest użycie typedefs dla typów pośrednich; na przykład:

typedef void foofun(char**, int);
foofun *foofunptr;

Lub, w pierwszym przykładzie "funkcja zwracająca wskaźnik do tablicy [] wskaźnika do funkcji zwracającej znak", możesz wykonać:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

W praktyce, jeśli piszesz cokolwiek rozsądnie, wiele z tych rzeczy będzie miało własne znaczące nazwy; na przykład mogą to być Funkcje zwracające nazwy (jakiegoś rodzaju), więc fun_returning_char może być name_generator_type, a array_of_ptrs_to_fun może być name_generator_list. Żebyś mógł zwiń go kilka linijek i zdefiniuj tylko te dwa typy-które prawdopodobnie będą przydatne gdzie indziej w każdym przypadku.

 2
Author: Brooks Moses,
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
2010-04-19 18:00:19
char far *far *ptr;

Jest to przestarzała forma Microsoftu, sięgająca czasów MS-DOS i bardzo wczesnych Windows. Jest to skrót od far pointer to a far pointer to a char, gdzie far pointer może wskazywać w dowolnym miejscu w pamięci, w przeciwieństwie do near pointer, który może wskazywać tylko w dowolnym miejscu w segmencie danych 64K. Naprawdę nie chcesz znać szczegółów na temat modeli pamięci Microsoftu do pracy wokół całkowicie martwej umysłowo architektury pamięci segmentowej Intel 80x86.

typedef void (*pfun)(int,float);

To deklaruje pfun jako typedef dla wskaźnika do procedury, która pobiera int i float. Zwykle używasz tego w deklaracji funkcji lub prototypie, mianowicie.

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}
 1
Author: John R. Strohm,
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
2009-09-19 16:24:02

Musimy oceniać wszystkie deklaracje wskaźnika od lewej do prawej strony, zaczynając od miejsca, w którym podano nazwę wskaźnika lub nazwę deklaracji.

Oceniając deklarację, musimy zacząć od najskrytszego nawiasu.

Zacznij od nazwy wskaźnika lub nazwy funkcji, po której następują znaki z prawej strony w nawiasach, a następnie folled przez znaki z lewej strony.

Przykład:

char (*(*f())[])();
         ^

char (*(*f())[])();
         ^^^
In here f is a function name, so we have to start from that.

F ()

char (*(*f())[])();
            ^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:

char (*(*f())[])();
      ^
f() *

Zakończyliśmy w nawiasach wewnętrznych znaki, a teraz musimy wrócić do jednego poziomu za tym:

char (*(*f())[])();

   ------

Teraz weź [], ponieważ to jest po prawej stronie bieżącego nawiasu.

char (*(*f())[])();
             ^^

f() * []

Teraz weź*, ponieważ nie ma znaku po prawej stronie.

char (*(*f())[])();
               ^

char (*(*f())[])();
      ^
f() * [] *

char (*(*f())[])();

Następnie oceń zewnętrzny nawias otwarty i zamknięty, wskazuje funkcję.

f() * [] * ()

char (*(*f())[])();

Teraz możemy dodać typ danych na końcu instrukcji.

f() * [] * () char.

char (*(*f())[])();

Finał odpowiedź:

    f() * [] * () char.

F jest funkcją zwracającą wskaźnik do tablicy[] wskaźników do funkcji zwracającej znak.

 1
Author: Peter Mortensen,
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 19:07:29

Zapomnij o 1 i 2 - to tylko teoria.

3: jest to używane w funkcji wprowadzania programu int main(int argc, char** argv). Możesz uzyskać dostęp do listy łańcuchów za pomocą char**. argv[0] = pierwszy ciąg, argv [1] = drugi ciąg, ...

 0
Author: Danvil,
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
2010-04-18 18:14:05

Przekazanie wskaźnika jako argumentu funkcji pozwala tej funkcji zmienić zawartość wskazywanej zmiennej, co może być przydatne do zwracania informacji za pomocą innych środków niż wartość zwracana przez funkcję. Na przykład zwracana wartość może być już użyta do wskazania błędu/sukcesu lub może być konieczne zwrócenie wielu wartości. Składnia tego w kodzie wywołującym to foo (&var), która przyjmuje adres var, tzn. wskaźnik do var.

Tak więc, jeśli zmienna, której zawartość, którą chcesz zmienić, to sam wskaźnik (np. łańcuch znaków), parametr zostanie zadeklarowany jako wskaźnik na wskaźnik .

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated

int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
    *str1 = some_defined_string ;
    *str2 = alloc_string() ;
    *str3 = "!!" ;

    if (str2 != 0) {
        return 0 ; //successful
    } else {
        return -1 ; //error
    }
}

main()
{
    char *s1 ; //uninitialized
    char *s2 ;
    char *s3 ;

    int success = point_me_to_the_strings(&s1, &s2, &s3) ;

    printf("%s%s%s", s1, s2, s3) ;
}

Zauważ, że main() nie przydziela żadnego miejsca na łańcuchy znaków, więc point_me_to_the_strings () nie zapisuje do str1, str2 i str3, tak jak gdyby zostały przekazane jako wskaźniki do znaków. Raczej point_me_to_the_strings () zmienia same wskaźniki, sprawiając, że wskazują one różne miejsca i może to zrobić, ponieważ ma wskazówki dla nich.

 0
Author: Paul Richter,
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
2010-04-21 02:05:08