Как я понимаю сложные объявления функций? - PullRequest
32 голосов
/ 19 сентября 2009

Как я понимаю следующие сложные объявления?

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 **));

Ответы [ 11 ]

72 голосов
/ 19 сентября 2009

Как уже отмечали другие, cdecl - правильный инструмент для работы.

Если вы хотите понять такое объявление без помощи cdecl, попробуйте прочитать изнутри и справа налево

Взяв один случайный пример из вашего списка char (*(*X[3])())[5];
Начните с X, который является объявленным / определенным идентификатором (и самым внутренним идентификатором):

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

X -

X[3]
 ^^^

X является массивом 3

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

X - массив из 3 указателей на

(*X[3])()
       ^^

X - это массив из 3 указателей на функцию , принимающую неопределенное (но фиксированное) количество аргументов

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

X - это массив из 3 указателей на функцию, принимающий неопределенное (но фиксированное) количество аргументов и возвращающий указатель

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

X - это массив из трех указателей на функцию, принимающий неопределенное (но фиксированное) количество аргументов и возвращающий указатель на массив из 5

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

X - это массив из трех указателей на функцию, принимающий неопределенное (но фиксированное) количество аргументов и возвращающий указатель на массив из 5 char .

16 голосов
/ 18 апреля 2010

Прочтите это изнутри, подобно тому, как вы будете решать уравнения, такие как {3+5*[2+3*(x+6*2)]}=0 - вы начнете с решения, что внутри (), затем [] и, наконец, {}:

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

Это означает, что x есть что-то .

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

x - это функция .

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

x возвращает указатель на что-то .

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

x возвращает указатель на массив .

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

x возвращает указатель на массив указателей .

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

x возвращает указатель на массив указателей на функции

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

Значение указателя массива, возвращаемого x, указывает на массив указателей на функции, которые указывают на функции, которые возвращают char .

Но да, используйте cdecl . Я сам использовал его, чтобы проверить свой ответ:).

Если это все еще сбивает вас с толку (и это, вероятно, должно), попробуйте сделать то же самое на листе бумаги или в вашем любимом текстовом редакторе. Нет возможности узнать, что это значит, просто взглянув на него.

13 голосов
/ 19 сентября 2009

Звучит как работа для инструмента cdecl:

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

Я искал официальную домашнюю страницу для инструмента, но не смог найти ту, которая казалась подлинной. В Linux вы можете ожидать, что ваш дистрибутив будет включать инструмент, поэтому я просто установил его, чтобы сгенерировать пример выше.

5 голосов
/ 19 сентября 2009

Вы должны использовать инструмент cdecl . Он должен быть доступен в большинстве дистрибутивов Linux.

например. для этой функции он вернет вам:

char (*(*f())[])(); - объявить f как функцию, возвращающую указатель на массив указателей на функцию, возвращающую char

void (*f)(int,void (*)()); - прототип указателя функции f. f - это функция, которая принимает два параметра, первый - int, а второй - указатель на функцию, возвращающую void.

char far *far *ptr; - ptr - это дальний указатель на дальний указатель (который указывает на некоторый символ / байт).

char (*(*X[3])())[5]; - X - это массив из 3 указателей на функцию, принимающий неопределенное количество аргументов и возвращающий указатель на массив из 5 символов.

typedef void (*pfun)(int,float); - объявление функции указателя pfun. pfun - это функция, которая принимает два параметра: первый - int, второй - типа float. функция не имеет возвращаемого значения;

например.

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

Кстати, сложные объявления, как последнее, встречаются не часто. Вот пример, который я только что сделал для этой цели.

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;
}
4 голосов
/ 19 апреля 2010

Похоже, что ваш настоящий вопрос таков:

Какой вариант использования указателя на указатель?

Указатель на указатель имеет тенденцию обнаруживаться, когда имеет массив некоторого типа T, а сам T является указателем на что-то еще. Например,

  • Что за строка в C? Как правило, это char *.
  • Хотели бы вы время от времени массив строк? Конечно.
  • Как бы вы объявили один? char *x[10]: x - это массив из 10 указателей на char, он же 10 строк.

В этот момент вам может быть интересно, куда входит char **. Он входит в картину из очень тесной связи между арифметикой указателей и массивами в C. Имя массива, x (почти) всегда преобразуется в указатель на его первый элемент.

  • Какой первый элемент? A char *.
  • Какой указатель на первый элемент? A char **.

В C массив E1[E2] определен равным *(E1 + E2). Обычно E1 - это имя массива, скажем, x, которое автоматически преобразуется в char **, а E2 - это некоторый индекс, скажем 3. (Это правило также объясняет, почему 3[x] и x[3] тоже самое.)

Указатели на указатели также отображаются, когда требуется динамически размещенный массив некоторого типа T, который сам по себе является указателем . Для начала давайте представим, что мы не знаем, что такое тип Т.

  • Если нам нужен динамически распределенный вектор T, какой тип нам нужен? T *vec.
  • Почему? Поскольку мы можем выполнять арифметику указателей в C, любой T * может служить основой непрерывной последовательности T в памяти.
  • Как мы можем выделить этот вектор, скажем, из n элементов? vec = malloc(n * sizeof(T));

Эта история верна для абсолютно любого типа T, и поэтому это верно для char *.

  • Какой тип vec, если T равен char *? char **vec.

Указатели на указатели также отображаются, когда у вас есть функция, которая должна изменить аргумент типа T, сама указатель .

  • Посмотрите на объявление для strtol: long strtol(char *s, char **endp, int b).
  • О чем это все? strtol преобразует строку из базы b в целое число. Он хочет сказать вам, как далеко в строку он попал. Возможно, он может вернуть структуру, содержащую long и char *, но это не так, как объявлено.
  • Вместо этого он возвращает свой второй результат, передавая адрес строки, которую он изменяет перед возвратом.
  • Что за строка? Ах да, char *.
  • Так, каков адрес строки? char **.

Если вы достаточно долго бродите по этому пути, вы также можете столкнуться с типами T ***, хотя вы почти всегда можете реструктурировать код, чтобы избежать их.

Наконец, указатели на указатели появляются в некоторых хитрых реализациях связанных списков . Рассмотрим стандартное объявление двусвязного списка в C.

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

Это работает нормально, хотя я не буду воспроизводить здесь функции вставки / удаления, но у него есть небольшая проблема. Любой узел можно удалить из списка (или вставить перед ним новый узел) без ссылки на заголовок списка. Ну, не совсем любой узел. Это не относится к первому элементу списка, где prev будет нулевым. Это может быть немного раздражающим в некоторых видах кода на C, когда вы больше работаете с самими узлами, чем со списком в качестве концепции. Это достаточно распространенное явление в низкоуровневом системном коде.

Что если мы переписаем node так:

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

В каждом узле prevp указывает не на предыдущий узел, а на указатель next предыдущих узлов. Как насчет первого узла? Это prevp очков на head. Если вы вытянете такой список (а у вас есть , чтобы нарисовать его, чтобы понять, как это работает), вы увидите, что вы можете удалить первый элемент или вставить новый узел перед первым элементом без явного ссылка head по имени.

2 голосов
/ 18 апреля 2010

x: функция, возвращающая указатель на массив [] указателя на функцию возвращающий символ "- а?

У вас есть функция

Эта функция возвращает указатель.

Этот указатель указывает на массив.

Этот массив является массивом указателей на функции (или указателей на функции)

Эти функции возвращают символ *.

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

Одним из них является облегчение возврата значений через аргументы.

Допустим, у вас есть

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

Вы называете это как

int x;
function(&x);

Как видите, чтобы function мог изменить наш x, мы должны передать ему указатель на наш x.

Что если x был не int, а char *? Ну, все равно, мы должны передать указатель на это. Указатель на указатель:

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

Вы называете это как

char *x;
function(&x); 
2 голосов
/ 18 апреля 2010

Remo.D ответ для чтения функций является хорошим предложением. Вот некоторые ответы на другие.

Один из вариантов использования указателя на указатель - это когда вы хотите передать его функции, которая изменит указатель. Например:

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

Кроме того, это может быть массив строк:

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

Как правило, один не должен использовать столь сложные объявления, хотя иногда вам нужны типы, которые довольно сложны для таких вещей, как указатели на функции. В этих случаях гораздо удобнее использовать typedef для промежуточных типов; например:

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

Или, для вашего первого примера «функция, возвращающая указатель на массив [] указателя на функцию, возвращающую символ», вы можете сделать:

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() { /*...*/ }

На практике, если вы пишете что-либо вменяемое, многие из этих вещей будут иметь собственные значимые имена; например, это могут быть функции, возвращающие имена (какого-то рода), поэтому fun_returning_char может быть name_generator_type, а array_of_ptrs_to_fun может быть name_generator_list. Таким образом, вы можете свернуть это несколькими строками и определить только эти два определения типа - которые, вероятно, пригодятся в любом другом месте.

1 голос
/ 24 апреля 2015

Мы должны оценить все операторы объявления указателя слева направо, начиная с того места, где в операторе объявлено имя указателя или имя объявления.

При оценке объявления мы должны начинать с самых внутренних скобок.

Начните с имени указателя или имени функции, за которыми следуют крайние правые символы в скобках и затем следуют крайние левые символы.

Пример:

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

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

F () * * 1 010

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() *

Мы завершили символы внутренней скобки, и теперь мы должны вернуться на один уровень позади этого:

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

   ------

Теперь возьмите [], потому что это справа от текущей круглой скобки.

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

f() * []

Теперь возьмите *, потому что справа нет символа.

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

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

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

Затем оцените внешнюю скобку открытия и закрытия, это указывает на функцию.

f() * [] * ()

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

Теперь мы можем добавить тип данных в конце оператора.

f() * [] * () char.

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

Окончательный ответ:

    f() * [] * () char.

f - это функция, возвращающая указатель на массив [] указателей на функцию, возвращающую символ.

1 голос
/ 19 сентября 2009
char far *far *ptr;

Это устаревшая форма Microsoft, относящаяся к MS-DOS и очень ранним дням Windows. Краткая версия заключается в том, что это дальний указатель на дальний указатель на символ, где дальний указатель может указывать в любом месте памяти, в отличие от ближнего указателя, который может указывать только в любом месте сегмента данных 64K. Вы действительно не хотите знать подробности о моделях памяти Microsoft для работы с совершенно мертвой архитектурой сегментированной памяти Intel 80x86.

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

Это объявляет pfun как typedef для указателя на процедуру, которая принимает int и float. Обычно вы используете это в объявлении функции или прототипе, а именно

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}
0 голосов
/ 19 апреля 2010

Передача указателя в качестве аргумента функции позволяет этой функции изменять содержимое указанной переменной, что может быть полезно для возврата информации другими способами, чем возвращаемое значение функции. Например, возвращаемое значение может уже использоваться для указания ошибки / успеха или может потребоваться вернуть несколько значений. Синтаксис этого в вызывающем коде - foo (& var), который принимает адрес var, то есть указатель на var.

Таким образом, если переменная, содержимое которой вы хотите изменить функцией, сама является указателем (например, строкой), параметр будет объявлен как указатель на указатель .

#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) ;
}

Обратите внимание, что main () не выделяет никакого хранилища для строк, поэтому point_me_to_the_strings () не записывает в str1, str2 и str3, как если бы они были переданы как указатели на символы. Скорее, point_me_to_the_strings () изменяет сами указатели, заставляя их указывать на разные места, и это может быть сделано, потому что на них есть указатели.

...