Подтвердить что ты не робот

Как я могу понять сложные объявления функций?

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

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

Ответ 1

Как указывали другие, 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 представляет собой массив из 3 указателей на функцию, принимающую неопределенное (но фиксированное) количество аргументов и возвращающее указатель в массив из 5

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

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

Ответ 2

Прочитайте это изнутри, подобно тому, как вы решаете уравнения, такие как {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. Я сам использовал его, чтобы проверить свой ответ:).

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

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

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

например.

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

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

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

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

Каков прецедент для указателя на указатель?

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

  • Какая строка в C? Как правило, это a 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;

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

Что делать, если мы перепишем node следующим образом:

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

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

Ответ 6

Ответ 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;

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

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. Таким образом, вы можете свернуть пару строк и определить только те два typedefs, которые, вероятно, будут полезны в любом другом месте.

Ответ 7

x: возвращающая функцию указатель на array [] указателя на функцию возвращение char "- huh?

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

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

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

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

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

 what 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, а a char *? Ну, его все равно то же самое, мы должны передать указатель на это. Указатель на указатель:

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

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

char *x;
function(&x); 

Ответ 8

char far *far *ptr;

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

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

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

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}

Ответ 9

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

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

Начните с имени или имени указателя, за которым следуют самые правые символы в родительском коде, а затем следующие левые символы.

Пример:

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

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

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

   ------

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

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

f() * []

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

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

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

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

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

f() * [] * ()

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

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

f() * [] * () char.

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

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

    f() * [] * () char.

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

Ответ 10

Забудьте о 1 и 2 - это просто теоретическое.

3: Это используется в функции ввода программы int main(int argc, char** argv). Вы можете получить доступ к списку строк с помощью char**. argv [0] = первая строка, argv [1] = вторая строка,...

Ответ 11

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

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

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it 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() изменяет сами указатели, заставляя их указывать на разные места, и это может сделать это, потому что у него есть указатели на них.