Страницы

Поиск по вопросам

четверг, 13 февраля 2020 г.

Как работает этот указатель на функцию?

#указатели #cpp #функции


void error(int i);
void (*p)(int);
p=&error;
(*p)(1);

Для начала попробую прочитать вот вторую строчку, то есть p - это указатель на функцию
возвращающую значения типа void. Почему я прочитал так и в частности "типа void", потому
что использовал методику отсюда. Но а по каким правилам это все происходит?
Я примерно понимаю это как : 


реализация функции(просто для примера, а так объявление тоже было, но его тут не
написал); 

уже читал его выше, дополню только то, что группирующие скобки нужны для того, чтобы
был указатель на функцию, а не имя функции и её возвращающий тип void*. Странно что
после прочтения одной книги по C++, я не встретил там упоминания о основном типе и
производном и по этому меня такая запись удивила, что уж говорить о такой int (*(*foo)())();,
это было бы вообще темным лесом, если бы не та методика.

Тут указателю "p" присваивается адрес, но адрес на что, на прототип функции?

Указатель разыменовывается и просто на его место подставляется имя функции error
и получается (error)(1)? То есть можно любую функцию оборачивать группирующими скобками
и от этого суть не меняется.  
 Такая запись нужна только потому что этого требует указатели, то есть чтобы указатель
имел тот же тип что и указываемый объект? 
    


Ответы

Ответ 1



Хороший вопрос. Вообще тема описания указателей на функции пожалуй самая запутанная в Си и особенно в С++. Наибольшие проблемы возникают с корректным для компилятора описанием функции, возвращающей адрес функции. Практически надо понимать, что указатель на функцию это адрес первой машинной команды тела функции и ее вызов производится путем загрузки этого адреса в регистр и выдачи инструкции call для регистра, например movq -16(%rbp), %rax // загрузка адреса функции call *%rax // вызов На практике удобно (согласен, что не совсем правильно, но реально работает) описывать адреса функций как void * и при необходимости использовать приведение типа. Например: (работает и в Си и в С++) // fu1.c вызов функций по адресу #include // если первый аргумент не 0, то // возвращает второй аргумент (передаваемую функцию), // иначе определенную в fu2.c функцию extern void *getfu(int, int (*f)(int)); // возвращает массив определенных в fu2.c функций extern void **getvfu(); static int qq (int i) // передаем эту функцию как аргумент в getfu() { return printf("qq=%d\n",i); } int main (int ac, char *av[]) { int i, (*p)(int); void **v = getvfu(); // получим массив функций из fu2.c for (i = 0; v[i]; i++) ((int (*)(int))(v[i]))(i); p = (int (*)(int))(av[1] ? getfu(0,0) : getfu(1,qq)); int rc = p(10); return printf ("rc = %d\n",rc) < 0; } // fu2.c функции и их массив #include static int fu (int i) { return printf ("fu=%d\n",i); } void * getfu (int fa, int (*f)(int)) { return (void *)(fa ? f : fu); } static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } void ** getvfu() { static void *fv[] = {(void *)f1, (void *)f2, (void *)f3, (void *)fu, 0}; return fv; } Транслируем и запускаем avp@avp-ubu1:~/hashcode$ g++ -c fu1.c fu2.c avp@avp-ubu1:~/hashcode$ g++ fu[12].o -o a++ avp@avp-ubu1:~/hashcode$ ./a++ 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ gcc fu1.c fu2.c avp@avp-ubu1:~/hashcode$ ./a.out 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ ./a.out f1: 0 f1: 1 f1: 2 fu=3 qq=10 rc = 6 avp@avp-ubu1:~/hashcode$ Возможно это (не совсем корректное решение) окажется полезным. Кстати, кто-нибудь может напишет действительно правильный (желательно собирающийся и работающий) вариант для С и С++.

Ответ 2



@avp Правильно -- это так? static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } static int fu (int i) { return printf ("fu=%d\n",i); } int (** getvfu())(int) { static int (*fv[])(int) = {f1, f2, f3, fu, 0}; return fv; } int main(int argc, char* argv[]) { int (**fv)(int) = getvfu(); fv[0](1); fv[1](2); fv[2](3); fv[3](4); return 0; } Случайно наткнулся ещё вот на что: Reading C type declarations @mzarb там узнал что переменные не передаются, а присваиваются в месте между телом функции и сигнатурой, Не совсем понятно, что Вы написали. Традиционно (допускаю, что не всегда и везде) параметры в функцию в C передаются через стек. Есть разные методы передачи параметров: "по значению" (передаётся значение, функция с ним может делать, что хочет, в вызывающей программе/функции значение параметра не меняется). В C все параметры именно так и передаются. Есть метод передачи "по ссылке" -- в функцию/подпрограмму передаётся ссылка на переменную (или значение, и там может получиться смешно, если функция будет это значение менять. Одна из типичных ошибок). В функции идёт работа с переменной-фактическим параметром через эту ссылку. Классика жанра -- фортран. В Си такого нет, но есть возможность смоделировать такую работу, передав в качестве параметра указатель на переменную. Сам указатель передаётся "по значению", а с переменной функция может делать всё, что захочет. Есть другие методы, например в алгол-60 был метод передачи параметров "по имени". В большинстве случаев выполнял те же задачи, что и метод передачи параметров "по ссылке", но имел некоторые дополнительные возможности (сейчас в некоторых языках есть подобные вещи -- параметры-блоки кода, вроде Obj-c, perl и пр.). Реализация -- thunk'и. Сигнатура тут ни причём. Она служит только для проверки правильности и добавки некоторых приведений типа по умолчанию.

Комментариев нет:

Отправить комментарий