как работают указатели на функции в c?

<p>у меня был некоторый опыт работы с указателями на функции в c.</p><p>продолжая традицию отвечать на ваши собственные вопросы, я решил сделать небольшое краткое изложение самых основ, для тех, кто нуждается в быстром погружении в предмет.</p>

<h1>указатели на функции в c</h1><p>давайте начнем с базовой функции, которой мы будем <em>указывая на</em>:</p><pre><code>int addint(int n, int m) { return n+m; } </code></pre><p>прежде всего, давайте определим указатель на функцию, которая получает 2 <code>int</code>s и возвращает <code>int</code>:</p><pre><code>int (*functionptr)(int,int); </code></pre><p>теперь мы можем смело указывать на нашу функцию:</p><pre><code>functionptr = &addint; </code></pre><p>теперь, когда у нас есть указатель на функцию, давайте использовать ее:</p><pre><code>int sum = (*functionptr)(2, 3); // sum == 5 </code></pre><p>передача указателя на другую функцию в основном такая же:</p><pre><code>int add2to3(int (*functionptr)(int, int)) { return (*functionptr)(2, 3); } </code></pre><p>мы также можем использовать указатели на функции в возвращаемых значениях (старайтесь не отставать, это становится грязным):</p><pre><code>// this is a function called functionfactory which receives parameter n // and returns a pointer to another function which receives two ints // and it returns another int int (*functionfactory(int n))(int, int) { printf("got parameter %d", n); int (*functionptr)(int,int) = &addint; return functionptr; } </code></pre><p>но гораздо приятнее использовать <code>typedef</code>:</p><pre><code>typedef int (*myfuncdef)(int, int); // note that the typedef name is indeed myfuncdef myfuncdef functionfactory(int n) { printf("got parameter %d", n); myfuncdef functionptr = &addint; return functionptr; } </code></pre>

<p><strong> функциональные указатели в c могут использоваться для выполнения объектно-ориентированного программирования в c. </strong></p><p>например, следующие строки написаны на c:</p><pre><code>string s1 = newstring(); s1->set(s1, "hello"); </code></pre><p>да, <code>-></code> и отсутствие оператора <code>new</code> бесполезны, но, похоже, это подразумевает, что мы устанавливаем текст некоторого класса <code>string</code> равным <code>"hello"</code>.</p><p>используя указатели функций, <strong> можно эмулировать методы в c </strong>.</p><p>как это достигается?</p><p>класс <code>string</code> на самом деле является <code>struct</code> с набором указателей на функции, которые служат способом имитации методов. ниже приведено частичное объявление класса <code>string</code>:</p><pre><code>typedef struct string\_struct* string; struct string\_struct { char* (*get)(const void* self); void (*set)(const void* self, char* value); int (*length)(const void* self); }; char* getstring(const void* self); void setstring(const void* self, char* value); int lengthstring(const void* self); string newstring(); </code></pre><p>как видно, методы класса <code>string</code> на самом деле являются указателями на функции для объявленной функции. при подготовке экземпляра <code>string</code> вызывается функция <code>newstring</code> для установки указателей функций на их соответствующие функции:</p><pre><code>string newstring() { string self = (string)malloc(sizeof(struct string\_struct)); self->get = &getstring; self->set = &setstring; self->length = &lengthstring; self->set(self, ""); return self; } </code></pre><p>например, функция <code>getstring</code>, которая вызывается путем вызова метода <code>get</code>, определяется следующим образом:</p><pre><code>char* getstring(const void* self\_obj) { return ((string)self\_obj)->internal->value; } </code></pre><p>одна вещь, которую можно заметить, заключается в том, что нет концепции экземпляра объекта и наличия методов, которые на самом деле являются частью объекта, поэтому "собственный объект" должен передаваться при каждом вызове. (а <code>internal</code> - это просто скрытое <code>struct</code>, которое было ранее опущено в листинге кода - это способ скрытия информации, но это не относится к указателям на функции.)</p><p>таким образом, вместо того, чтобы выполнять <code>s1->set("hello");</code>, необходимо передать объект для выполнения действия с <code>s1->set(s1, "hello")</code>.</p><p>с этим небольшим объяснением, которое нужно передать в виде ссылки на себя, мы перейдем к следующей части, которая является <strong> наследованием в c </strong>.</p><p>допустим, мы хотим создать подкласс <code>string</code>, скажем <code>immutablestring</code>. чтобы сделать строку неизменной, метод <code>set</code> будет недоступен, при этом поддерживается доступ к <code>get</code> и <code>length</code>, и вынудите "конструктор" принять <code>char*</code>:</p><pre><code>typedef struct immutablestring\_struct* immutablestring; struct immutablestring\_struct { string base; char* (*get)(const void* self); int (*length)(const void* self); }; immutablestring newimmutablestring(const char* value); </code></pre><p>по сути, для всех подклассов доступные методы снова являются указателями на функции. на этот раз объявление для метода <code>set</code> отсутствует, поэтому его нельзя вызвать в <code>immutablestring</code>.</p><p>что касается реализации <code>immutablestring</code>, единственным соответствующим кодом является функция "конструктор", <code>newimmutablestring</code>:</p><pre><code>immutablestring newimmutablestring(const char* value) { immutablestring self = (immutablestring)malloc(sizeof(struct immutablestring\_struct)); self->base = newstring(); self->get = self->base->get; self->length = self->base->length; self->base->set(self->base, (char*)value); return self; } </code></pre><p>при создании экземпляра <code>immutablestring</code> функция указателей на методы <code>get</code> и <code>length</code> фактически ссылается на метод <code>string.get</code> и <code>string.length</code>, проходя через переменную <code>base</code>, которая является внутренне сохраненным объектом <code>string</code>.</p><p>использование указателя на функцию может обеспечить наследование метода от суперкласса.</p><p>далее мы можем продолжить <strong> полиморфизм в c </strong>.</p><p>если, например, мы хотим изменить поведение метода <code>length</code>, чтобы он по какой-то причине все время возвращал <code>0</code> в классе <code>immutablestring</code>, все, что нужно сделать, это:</p><ol> <li>добавьте функцию, которая будет служить переопределяющим методом <code>length</code>.</li> <li>перейдите к "конструктору" и установите указатель функции на переопределенный метод <code>length</code>.</li> </ol><p>добавление переопределенного метода <code>length</code> в <code>immutablestring</code> может быть выполнено путем добавления <code>lengthoverridemethod</code>:</p><pre><code>int lengthoverridemethod(const void* self) { return 0; } </code></pre><p>затем указатель функции для метода <code>length</code> в конструкторе подключается к <code>lengthoverridemethod</code>:</p><pre><code>immutablestring newimmutablestring(const char* value) { immutablestring self = (immutablestring)malloc(sizeof(struct immutablestring\_struct)); self->base = newstring(); self->get = self->base->get; self->length = &lengthoverridemethod; self->base->set(self->base, (char*)value); return self; } </code></pre><p>теперь вместо того, чтобы иметь идентичное поведение для метода <code>length</code> в классе <code>immutablestring</code> с классом <code>string</code>, теперь метод <code>length</code> будет ссылаться на поведение, определенное в функции <code>lengthoverridemethod</code>.</p><p>я должен добавить заявление об отказе от ответственности, которое я все еще изучаю, как писать в объектно-ориентированном стиле программирования на c, так что, вероятно, есть моменты, которые я не очень хорошо объяснил, или они могут быть просто неуместны с точки зрения того, как лучше всего реализовать oop в c. но моя цель состояла в том, чтобы попытаться проиллюстрировать одно из многих применений указателей функций.</p><p>для получения дополнительной информации о том, как выполнить объектно-ориентированное программирование на c, пожалуйста, обратитесь к следующим вопросам:</p><ul> <li> <a href="https://stackoverflow.com/questions/415452/object-orientation-in-c">объектная ориентация в c?</a> </li> <li> <a href="https://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c">можете ли вы написать объектно-ориентированный код на c?</a> </li> </ul>

<p>руководство по увольнению: как злоупотреблять указателями функций в gcc на компьютерах с архитектурой x86 путем компиляции кода вручную:</p><p>эти строковые литералы являются байтами 32-битного машинного кода x86. <code>0xc3</code> is <a href="http://felixcloutier.com/x86/ret.html">инструкция <code>ret</code> x86</a> .</p><p>обычно вы не пишете их вручную, вы пишете на языке ассемблера, а затем используете ассемблер, такой как <code>nasm</code>, чтобы собрать его в плоский двоичный файл, который вы превращаете в шестнадцатеричный строковый литерал.</p><ol> <li> <p>возвращает текущее значение в регистре eax</p> <pre><code>int eax = ((int(*)())("\xc3 <- this returns the value of the eax register"))(); </code></pre></li> <li> <p>написать функцию подкачки</p> <pre><code>int a = 10, b = 20; ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- this swaps the values of a and b")(&a,&b); </code></pre></li> <li> <p>запишите счетчик цикла в 1000, каждый раз вызывая некоторую функцию</p> <pre><code>((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000 </code></pre></li> <li> <p>вы даже можете написать рекурсивную функцию, которая считает до 100</p> <pre><code>const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- recursively calls the function at address lol."; i = ((int(*)())(lol))(lol); </code></pre></li> </ol><p>обратите внимание, что компиляторы помещают строковые литералы в раздел <code>.rodata</code> (или <code>.rdata</code> в windows), который связан как часть текстового сегмента (вместе с кодом для функций).</p><p>текстовый сегмент имеет разрешение read + exec, поэтому приведение строковых литералов к указателям на функции работает без необходимости использования системных вызовов <code>mprotect()</code> или <code>virtualprotect()</code>, как если бы вам потребовалась динамически распределенная память. (или <code>gcc -z execstack</code> связывает программу со стеком + сегмент данных + исполняемый файл кучи, как быстрый взлом.)</p><hr><p>чтобы разобрать их, вы можете скомпилировать их, чтобы поместить метку в байты, и использовать дизассемблер.</p><pre><code>// at global scope const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- this swaps the values of a and b"; </code></pre><p>компилируя с <code>gcc -c -m32 foo.c</code> и разбирая с <code>objdump -d -rwc -mintel</code>, мы можем получить сборку и выяснить, что этот код нарушает abi, забивая ebx (регистр, сохраняющий вызов), и, как правило, неэффективен.</p><pre><code>00000000 <swap>: 0: 8b 44 24 04 mov eax,dword ptr [esp+0x4] # load int *a arg from the stack 4: 8b 5c 24 08 mov ebx,dword ptr [esp+0x8] # ebx = b 8: 8b 00 mov eax,dword ptr [eax] # dereference: eax = *a a: 8b 1b mov ebx,dword ptr [ebx] c: 31 c3 xor ebx,eax # pointless xor-swap e: 31 d8 xor eax,ebx # instead of just storing with opposite registers 10: 31 c3 xor ebx,eax 12: 8b 4c 24 04 mov ecx,dword ptr [esp+0x4] # reload a from the stack 16: 89 01 mov dword ptr [ecx],eax # store to *a 18: 8b 4c 24 08 mov ecx,dword ptr [esp+0x8] 1c: 89 19 mov dword ptr [ecx],ebx 1e: c3 ret not shown: the later bytes are ascii text documentation they're not executed by the cpu because the ret instruction sends execution back to the caller </code></pre><p>этот машинный код (вероятно) будет работать в 32-битном коде в windows, linux, os x и т.д.: соглашения о вызовах по умолчанию во всех этих ос передают аргументы в стеке, а не более эффективно в регистрах. но ebx сохраняется во всех обычных соглашениях о вызовах, поэтому использование его в качестве рабочего регистра без сохранения/восстановления может легко вызвать сбой вызывающего абонента.</p>

<p>одно из моих любимых применений для указателей на функции - это дешевые и простые итераторы -</p><pre><code>#include <stdio.h> #define max\_colors 256 typedef struct { char* name; int red; int green; int blue; } color; color colors[max\_colors]; void eachcolor (void (*fp)(color *c)) { int i; for (i=0; i<max\_colors; i++) (*fp)(&colors[i]); } void printcolor(color* c) { if (c->name) printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue); } int main() { colors[0].name="red"; colors[0].red=255; colors[1].name="blue"; colors[1].blue=255; colors[2].name="black"; eachcolor(printcolor); } </code></pre>

<p>указатели на функции легко объявляются, когда у вас есть основные деклараторы:</p><ul> <li>id: <code>id</code>: <em>идентификатор - это</em></li> <li>указатель: <code>*d</code>: <em>d указатель на</em></li> <li>функция: <code>d(<parameters>)</code>: <em>функция d принимает <code><</code>parameters<code>></code> возвращая</em></li> </ul><p>в то время как d - еще один декларатор, созданный по тем же правилам. в конце концов, где-то он заканчивается на <code>id</code> (пример приведен ниже), который является именем объявленной сущности. давайте попробуем построить функцию, которая берет указатель на функцию, которая ничего не берет и возвращает int, и возвращает указатель на функцию, которая принимает char и возвращает int. с type-def это так</p><pre><code>typedef int returnfunction(char); typedef int parameterfunction(void); returnfunction *f(parameterfunction *p); </code></pre><p>как видите, его довольно легко собрать с помощью typedefs. без typedefs это не сложно и с указанными выше правилами декларатора, применяемыми последовательно. как вы видите, я пропустил ту часть, на которую указывает указатель, и функцию, которую возвращает функция. это то, что появляется в самом левом углу объявления, и не представляет интереса: оно добавляется в конце, если уже создан декларатор. давайте сделаем это. создайте его последовательно, сначала многословно - показ структуры с использованием <code>[</code> и <code>]</code>:</p><pre><code>function taking [pointer to [function taking [void] returning [int]]] returning [pointer to [function taking [char] returning [int]]] </code></pre><p>как видите, можно полностью описать тип, добавив деклараторы один за другим. строительство может быть сделано двумя способами. один - снизу вверх, начиная с самой правильной вещи (уходит) и проходя путь до идентификатора. другой способ - сверху вниз, начиная с идентификатора, работая вниз до листьев. я покажу оба пути.</p><h2>вверх дном</h2><p>построение начинается с правильной вещи: возвращенная вещь, то есть функция, принимающая символ. чтобы отличить деклараторов, я собираюсь перечислить их:</p><pre><code>d1(char); </code></pre><p>вставил параметр char напрямую, так как он тривиален. добавление указателя на декларатор путем замены <code>d1</code> на <code>*d2</code>. обратите внимание, что мы должны заключить скобки в <code>*d2</code>. это можно узнать, посмотрев приоритет <code>*-operator</code> и оператора вызова функции <code>()</code>. без наших круглых скобок компилятор прочитал бы его как <code>*(d2(char p))</code>. но это, конечно, больше не будет простой заменой d1 на <code>*d2</code>. круглые скобки всегда разрешены вокруг деклараторов. таким образом, вы не сделаете ничего плохого, если добавите слишком много из них, на самом деле.</p><pre><code>(*d2)(char); </code></pre><p>тип возврата завершен! теперь давайте заменим <code>d2</code> на декларатор функции <em>функция принимает <code><parameters></code> returning</em>, то есть <code>d3(<parameters>)</code>, которой мы сейчас являемся.</p><pre><code>(*d3(<parameters>))(char) </code></pre><p>обратите внимание, что круглые скобки не нужны, так как мы <em>хотим</em> <code>d3</code> быть на этот раз декларатором функции, а не декларатором указателя. отлично, осталось только параметры для него. параметр выполняется точно так же, как мы сделали возвращаемый тип, только с <code>char</code>, замененным на <code>void</code>. поэтому я скопирую это:</p><pre><code>(*d3( (*id1)(void)))(char) </code></pre><p>я заменил <code>d2</code> на <code>id1</code>, так как мы закончили с этим параметром (это уже указатель на функцию - нет необходимости в другом деклараторе). <code>id1</code> будет именем параметра. теперь, как я сказал выше, в конце добавляется тип, который модифицируют все эти деклараторы - тот, который появляется слева от каждого объявления. для функций это становится типом возврата. для указателей, указывающих на тип и т.д. интересно, что когда вы записываете тип, он будет отображаться в обратном порядке, в самом праве :) в любом случае, его замена приводит к полному объявлению. оба раза <code>int</code> конечно.</p><pre><code>int (*id0(int (*id1)(void)))(char) </code></pre><p>я вызвал идентификатор функции <code>id0</code> в этом примере.</p><h2>низходящий</h2><p>это начинается с идентификатора слева от описания типа, оборачивая этот декларатор, пока мы идем по правому пути. начните с <em>функция принимает <code><</code>parameters<code>></code> возвращение</em></p><pre><code>id0(<parameters>) </code></pre><p>следующая вещь в описании (после "возврата") была <em>указатель на</em>. давайте включим это:</p><pre><code>*id0(<parameters>) </code></pre><p>затем следующая вещь была <em>функция принимает <code><</code>parameters<code>></code> возвращение</em>. параметр представляет собой простой символ, поэтому мы сразу добавляем его, поскольку он действительно тривиален.</p><pre><code>(*id0(<parameters>))(char) </code></pre><p>обратите внимание на скобки, которые мы добавили, поскольку мы снова хотим, чтобы <code>*</code> связывался первым, а <em>затем</em> <code>(char)</code>. в противном случае он будет читать <em>функция принимает <code><</code>parameters<code>></code> возвращающую функцию ...</em>. нет, функции, возвращающие функции, даже не разрешены.</p><p>теперь нам просто нужно поставить <code><</code>parameters<code>></code>. я покажу краткую версию вывода, так как я думаю, что у вас уже есть идея, как это сделать.</p><pre><code>pointer to: *id1 ... function taking void returning: (*id1)(void) </code></pre><p>просто поместите <code>int</code> перед деклараторами, как мы сделали снизу вверх, и мы закончили</p><pre><code>int (*id0(int (*id1)(void)))(char) </code></pre><h2>хорошая вещь</h2><p>лучше снизу вверх или сверху вниз? я привык снизу вверх, но некоторые люди могут чувствовать себя более комфортно с сверху вниз. это вопрос вкуса, я думаю. кстати, если вы примените все операторы в этой декларации, вы получите int:</p><pre><code>int v = (*id0(some\_function\_pointer))(some\_char); </code></pre><p>это свойство nice объявлений в c: объявление утверждает, что если эти операторы используются в выражении, использующем идентификатор, то он выдает тип слева. так же и для массивов.</p><p>надеюсь, вам понравился этот маленький урок! теперь мы можем ссылаться на это, когда люди задаются вопросом о странном синтаксисе объявления функций. я пытался поставить как можно меньше c внутренних органов. не стесняйтесь редактировать/исправлять вещи в нем.</p>

<h2>еще одно хорошее использование для указателей функций:<br> переключение между версиями безболезненно</h2><p>их очень удобно использовать, когда вам нужны разные функции в разное время или на разных этапах разработки. например, я разрабатываю приложение на хост-компьютере с консолью, но окончательный выпуск программного обеспечения будет помещен на avnet zedboard (с портами для дисплеев и консолей, но они не нужны/не нужны для окончательный релиз). поэтому во время разработки я буду использовать <code>printf</code> для просмотра сообщений о состоянии и ошибках, но когда я закончу, я не хочу ничего печатать. вот что я сделал:</p><h1>version.h</h1><pre><code>// first, undefine all macros associated with version.h #undef debug\_version #undef release\_version #undef invalid\_version // define which version we want to use #define debug\_version // the current version // #define release\_version // to be uncommented when finished debugging #ifndef \_\_version\_h\_ /* prevent circular inclusions */ #define \_\_version\_h\_ /* by using protection macros */ void board\_init(); void noprintf(const char *c, ...); // mimic the printf prototype #endif // mimics the printf function prototype. this is what i'll actually // use to print stuff to the screen void (* zprintf)(const char*, ...); // if debug version, use printf #ifdef debug\_version #include <stdio.h> #endif // if both debug and release version, error #ifdef debug\_version #ifdef release\_version #define invalid\_version #endif #endif // if neither debug or release version, error #ifndef debug\_version #ifndef release\_version #define invalid\_version #endif #endif #ifdef invalid\_version // won't allow compilation without a valid version define #error "invalid version definition" #endif </code></pre><p>в <code>version.c</code> я определю 2 прототипа функций, представленных в <code>version.h</code></p><h1>version.c</h1><pre><code>#include "version.h" /*****************************************************************************/ /** * @name board\_init * * sets up the application based on the version type defined in version.h. * includes allowing or prohibiting printing to stdout. * * must be called first thing in main * * @return none * *****************************************************************************/ void board\_init() { // assign the print function to the correct function pointer #ifdef debug\_version zprintf = &printf; #else // defined below this function zprintf = &noprintf; #endif } /*****************************************************************************/ /** * @name noprintf * * simply returns with no actions performed * * @return none * *****************************************************************************/ void noprintf(const char* c, ...) { return; } </code></pre><p>обратите внимание, как указатель на функцию прототипируется в <code>version.h</code> как</p><p><code>void (* zprintf)(const char *, ...);</code></p><p>когда на него ссылаются в приложении, оно начинает исполняться, куда бы оно ни указывало, что еще предстоит определить.</p><p>в <code>version.c</code> обратите внимание на функцию <code>board\_init()</code>, где <code>zprintf</code> назначается уникальная функция (чья сигнатура функции совпадает) в зависимости от версии, определенной в <code>version.h</code></p><p><code>zprintf = &printf;</code> zprintf вызывает printf для целей отладки</p><p>или же</p><p><code>zprintf = &noprint;</code> zprintf просто возвращается и не будет запускать ненужный код</p><p>запуск кода будет выглядеть так:</p><h1>mainprog.c</h1><pre><code>#include "version.h" #include <stdlib.h> int main() { // must run board\_init(), which assigns the function // pointer to an actual function board\_init(); void *ptr = malloc(100); // allocate 100 bytes of memory // malloc returns null if unable to allocate the memory. if (ptr == null) { zprintf("unable to allocate memory\n"); return 1; } // other things to do... return 0; } </code></pre><p>приведенный выше код будет использовать <code>printf</code> в режиме отладки или ничего не делать в режиме выпуска. это гораздо проще, чем просматривать весь проект, комментировать или удалять код. все, что мне нужно сделать, это изменить версию в <code>version.h</code>, а код сделает все остальное!</p>

<p>указатель на функцию обычно определяется <code>typedef</code> и используется в качестве параметра и возвращаемого значения.</p><p>выше ответы уже многое объяснили, просто приведу полный пример:</p><pre><code>#include <stdio.h> #define num\_a 1 #define num\_b 2 // define a function pointer type typedef int (*two\_num\_operation)(int, int); // an actual standalone function static int sum(int a, int b) { return a + b; } // use function pointer as param, static int sum\_via\_pointer(int a, int b, two\_num\_operation funp) { return (*funp)(a, b); } // use function pointer as return value, static two\_num\_operation get\_sum\_fun() { return ∑ } // test - use function pointer as variable, void test\_pointer\_as\_variable() { // create a pointer to function, two\_num\_operation sum\_p = ∑ // call function via pointer printf("pointer as variable:\t %d + %d = %d\n", num\_a, num\_b, (*sum\_p)(num\_a, num\_b)); } // test - use function pointer as param, void test\_pointer\_as\_param() { printf("pointer as param:\t %d + %d = %d\n", num\_a, num\_b, sum\_via\_pointer(num\_a, num\_b, &sum)); } // test - use function pointer as return value, void test\_pointer\_as\_return\_value() { printf("pointer as return value:\t %d + %d = %d\n", num\_a, num\_b, (*get\_sum\_fun())(num\_a, num\_b)); } int main() { test\_pointer\_as\_variable(); test\_pointer\_as\_param(); test\_pointer\_as\_return\_value(); return 0; } </code></pre>

<p>одним из наиболее важных применений указателей функций в c является вызов функции, выбранной во время выполнения. например, библиотека времени выполнения c имеет две подпрограммы: <a href="https://en.cppreference.com/w/c/algorithm/qsort"><code>qsort</code></a> и <a href="https://en.cppreference.com/w/c/algorithm/bsearch"><code>bsearch</code></a> , которые принимают указатель на функцию, которая вызывается для сравнения двух сортируемых элементов; это позволяет вам сортировать или искать, соответственно, что угодно, основываясь на любых критериях, которые вы хотите использовать.</p><p>очень простой пример, если есть одна функция с именем <code>print(int x, int y)</code>, которая, в свою очередь, может потребовать вызова функции (<code>add()</code> или <code>sub()</code>, которые того же типа), то, что мы будем делать, мы добавим один аргумент-указатель на функцию к <code>print()</code> функция как показано ниже:</p><pre><code>#include <stdio.h> int add() { return (100+10); } int sub() { return (100-10); } void print(int x, int y, int (*func)()) { printf("value is: %d\n", (x+y+(*func)())); } int main() { int x=100, y=200; print(x,y,add); print(x,y,sub); return 0; } </code></pre><p>результат:</p><blockquote> <p>значение: 410<br> значение: 390</p> </blockquote>

<p>указатель на функцию - это переменная, которая содержит адрес функции. поскольку это переменная-указатель, хотя и с некоторыми ограниченными свойствами, вы можете использовать ее почти так же, как и любую другую переменную-указатель в структурах данных.</p><p>единственное исключение, о котором я могу думать, - это обращение к указателю на функцию, указывающее на что-то, отличное от одного значения. делать арифметику указателя, увеличивая или уменьшая указатель на функцию или добавляя/вычитая смещение к указателю на функцию, на самом деле не имеет никакого смысла, так как указатель на функцию указывает только на одну вещь, точку входа в функцию.</p><p>размер переменной указателя функции, количество байтов, занимаемых этой переменной, может варьироваться в зависимости от базовой архитектуры, например, х32 или х64 или что угодно.</p><p>объявление для переменной указателя функции должно указывать ту же информацию, что и объявление функции, чтобы компилятор c мог выполнять те виды проверок, которые он обычно делает. если вы не укажете список параметров в объявлении/определении указателя функции, компилятор c не сможет проверить использование параметров. есть случаи, когда это отсутствие проверки может быть полезным, однако просто помните, что сеть безопасности была удалена.</p><p>некоторые примеры:</p><pre><code>int func (int a, char *pstr); // declares a function int (*pfunc)(int a, char *pstr); // declares or defines a function pointer int (*pfunc2) (); // declares or defines a function pointer, no parameter list specified. int (*pfunc3) (void); // declares or defines a function pointer, no arguments. </code></pre><p>первые два объявления несколько похожи в этом:</p><ul> <li><code>func</code> - это функция, которая принимает <code>int</code> и <code>char *</code> и возвращает <code>int</code></li> <li><code>pfunc</code> - это указатель на функцию, которой присваивается адрес функции, которая принимает <code>int</code> и <code>char *</code> и возвращает <code>int</code></li> </ul><p>таким образом, из вышесказанного может быть строка источника, в которой адрес функции <code>func()</code> присваивается переменной указателя функции <code>pfunc</code>, как в <code>pfunc = func;</code>.</p><p>обратите внимание на синтаксис, используемый с объявлением/определением указателя функции, в котором скобки используются для преодоления естественных правил приоритета операторов.</p><pre><code>int *pfunc(int a, char *pstr); // declares a function that returns int pointer int (*pfunc)(int a, char *pstr); // declares a function pointer that returns an int </code></pre><p><strong>несколько разных примеров использования</strong></p><p>некоторые примеры использования указателя на функцию:</p><pre><code>int (*pfunc) (int a, char *pstr); // declare a simple function pointer variable int (*pfunc[55])(int a, char *pstr); // declare an array of 55 function pointers int (**pfunc)(int a, char *pstr); // declare a pointer to a function pointer variable struct { // declare a struct that contains a function pointer int x22; int (*pfunc)(int a, char *pstr); } thing = {0, func}; // assign values to the struct variable char * xf (int x, int (*p)(int a, char *pstr)); // declare a function that has a function pointer as an argument char * (*pxf) (int x, int (*p)(int a, char *pstr)); // declare a function pointer that points to a function that has a function pointer as an argument </code></pre><p>вы можете использовать списки параметров переменной длины в определении указателя функции.</p><pre><code>int sum (int a, int b, ...); int (*psum)(int a, int b, ...); </code></pre><p>или вы не можете указать список параметров вообще. это может быть полезно, но исключает возможность для компилятора c выполнять проверки по предоставленному списку аргументов.</p><pre><code>int sum (); // nothing specified in the argument list so could be anything or nothing int (*psum)(); int sum2(void); // void specified in the argument list so no parameters when calling this function int (*psum2)(void); </code></pre><p><strong>c стиль cast</strong></p><p>вы можете использовать приведения в стиле c с указателями на функции. однако имейте в виду, что компилятор c может быть небрежным в отношении проверок или предоставлять предупреждения, а не ошибки.</p><pre><code>int sum (int a, char *b); int (*psplsum) (int a, int b); psplsum = sum; // generates a compiler warning psplsum = (int (*)(int a, int b)) sum; // no compiler warning, cast to function pointer psplsum = (int *(int a, int b)) sum; // compiler error of bad cast generated, parenthesis are required. </code></pre><p><strong>сравнить указатель на функцию с равенством</strong></p><p>вы можете проверить, что указатель на функцию равен определенному адресу функции, используя оператор <code>if</code>, хотя я не уверен, насколько это будет полезно. другие операторы сравнения могут показаться еще менее полезными.</p><pre><code>static int func1(int a, int b) { return a + b; } static int func2(int a, int b, char *c) { return c[0] + a + b; } static int func3(int a, int b, char *x) { return a + b; } static char *func4(int a, int b, char *c, int (*p)()) { if (p == func1) { p(a, b); } else if (p == func2) { p(a, b, c); // warning c4047: '==': 'int (\_\_cdecl *)()' differs in levels of indirection from 'char *(\_\_cdecl *)(int,int,char *)' } else if (p == func3) { p(a, b, c); } return c; } </code></pre><p><strong>массив указателей на функции</strong></p><p>и если вы хотите иметь массив указателей на функции, каждый из элементов которых имеет список аргументов, то вы можете определить указатель функции с неопределенным списком аргументов (не <code>void</code>, что означает отсутствие аргументов, а просто неуказанный), что-то вроде следующего, хотя вы можете увидеть предупреждения от компилятора c. это также работает для параметра указателя на функцию:</p><pre><code>int(*p[])() = { // an array of function pointers func1, func2, func3 }; int(**pp)(); // a pointer to a function pointer p[0](a, b); p[1](a, b, 0); p[2](a, b); // oops, left off the last argument but it compiles anyway. func4(a, b, 0, func1); func4(a, b, 0, func2); // warning c4047: 'function': 'int (\_\_cdecl *)()' differs in levels of indirection from 'char *(\_\_cdecl *)(int,int,char *)' func4(a, b, 0, func3); // iterate over the array elements using an array index for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) { func4(a, b, 0, p[i]); } // iterate over the array elements using a pointer for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) { (*pp)(a, b, 0); // pointer to a function pointer so must dereference it. func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it. } </code></pre><p><strong>стиль <code>namespace</code> в стиле c с использованием глобального <code>struct</code> с указателями на функции</strong></p><p>вы можете использовать ключевое слово <code>static</code>, чтобы указать функцию, имя которой является областью действия файла, а затем назначить ее глобальной переменной, чтобы обеспечить нечто похожее на функциональность <code>namespace</code> в c++.</p><p>в заголовочном файле определите структуру, которая будет нашим пространством имен, вместе с глобальной переменной, которая ее использует.</p><pre><code>typedef struct { int (*func1) (int a, int b); // pointer to function that returns an int char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer } functhings; extern const functhings functhingsglobal; </code></pre><p>затем в исходном файле c:</p><pre><code>#include "header.h" // the function names used with these static functions do not need to be the // same as the struct member names. it's just helpful if they are when trying // to search for them. // the static keyword ensures these names are file scope only and not visible // outside of the file. static int func1 (int a, int b) { return a + b; } static char *func2 (int a, int b, char *c) { c[0] = a % 100; c[1] = b % 50; return c; } const functhings functhingsglobal = {func1, func2}; </code></pre><p>затем это можно использовать, указав полное имя глобальной переменной структуры и имя члена для доступа к функции. модификатор <code>const</code> используется на глобальном уровне, поэтому его нельзя изменить случайно.</p><pre><code>int abcd = functhingsglobal.func1 (a, b); </code></pre><p><strong>области применения функциональных указателей</strong></p><p>библиотечный компонент dll может делать что-то похожее на подход <code>namespace</code> в стиле c, в котором конкретный интерфейс библиотеки запрашивается из фабричного метода в интерфейсе библиотеки, который поддерживает создание <code>struct</code>, содержащего указатели на функции. эта библиотека интерфейс загружает запрошенную версию dll, создает структуру с необходимыми указателями на функции, а затем возвращает структуру запрашивающей вызывающей стороне для использования.</p><pre><code>typedef struct { hmodule hmodule; int (*func1)(); int (*func2)(); int(*func3)(int a, int b); } libraryfuncstruct; int loadlibraryfunc lpctstr dllfilename, libraryfuncstruct *pstruct) { int retstatus = 0; // default is an error detected pstruct->hmodule = loadlibrary (dllfilename); if (pstruct->hmodule) { pstruct->func1 = (int (*)()) getprocaddress (pstruct->hmodule, "func1"); pstruct->func2 = (int (*)()) getprocaddress (pstruct->hmodule, "func2"); pstruct->func3 = (int (*)(int a, int b)) getprocaddress(pstruct->hmodule, "func3"); retstatus = 1; } return retstatus; } void freelibraryfunc (libraryfuncstruct *pstruct) { if (pstruct->hmodule) freelibrary (pstruct->hmodule); pstruct->hmodule = 0; } </code></pre><p>и это может быть использовано как в:</p><pre><code>libraryfuncstruct mylib = {0}; loadlibraryfunc (l"library.dll", &mylib); // .... mylib.func1(); // .... freelibraryfunc (&mylib); </code></pre><p>тот же подход можно использовать для определения абстрактного аппаратного уровня для кода, который использует конкретную модель базового аппаратного обеспечения. указатели функций заполняются фабрично-специфическими функциями для обеспечения аппаратно-специфической функциональности, которая реализует функции, указанные в абстрактной аппаратной модели. это может быть использовано для предоставления абстрактного аппаратного уровня, используемого программным обеспечением, которое вызывает заводскую функцию, чтобы получить интерфейс конкретной аппаратной функции, а затем использует указатели функций, предоставленные для выполнения действий для базового аппаратного обеспечения, без необходимости знать подробности реализации о конкретной цели. ,.</p><p><strong>указатели на функции для создания делегатов, обработчиков и обратных вызовов</strong></p><p>вы можете использовать указатели функций как способ делегировать некоторые задачи или функции. классическим примером в c является указатель функции делегата сравнения, используемый с функциями библиотеки standard c <code>qsort()</code> и <code>bsearch()</code>, чтобы обеспечить порядок сортировки для сортировки списка элементов или выполнения двоичного поиска по отсортированному списку элементов. делегат функции сравнения определяет алгоритм сортировки, используемый при сортировке или двоичном поиске.</p><p>другое использование аналогично применению алгоритма к контейнеру стандартной библиотеки шаблонов c++.</p><pre><code>void * applyalgorithm (void *parray, size\_t sizeitem, size\_t nitems, int (*p)(void *)) { unsigned char *plist = parray; unsigned char *plistend = plist + nitems * sizeitem; for ( ; plist < plistend; plist += sizeitem) { p (plist); } return parray; } int pincrement(int *pi) { (*pi)++; return 1; } void * applyfold(void *parray, size\_t sizeitem, size\_t nitems, void * presult, int(*p)(void *, void *)) { unsigned char *plist = parray; unsigned char *plistend = plist + nitems * sizeitem; for (; plist < plistend; plist += sizeitem) { p(plist, presult); } return parray; } int psummation(int *pi, int *psum) { (*psum) += *pi; return 1; } // source code and then lets use our function. int intlist[30] = { 0 }, isum = 0; applyalgorithm(intlist, sizeof(int), sizeof(intlist) / sizeof(intlist[0]), pincrement); applyfold(intlist, sizeof(int), sizeof(intlist) / sizeof(intlist[0]), &isum, psummation); </code></pre><p>другой пример - с исходным кодом gui, в котором зарегистрирован обработчик для определенного события, предоставляя указатель на функцию, которая фактически вызывается, когда происходит событие. платформа microsoft mfc с ее картами сообщений использует нечто подобное для обработки сообщений windows, доставляемых в окно или поток.</p><p>асинхронные функции, требующие обратного вызова, аналогичны обработчику событий. пользователь асинхронной функции вызывает асинхронную функцию для запуска какого-либо действия и предоставляет указатель на функцию, которую асинхронная функция будет вызывать после завершения действия. в этом случае событие является асинхронной функцией, выполняющей свою задачу.</p>

<p>функция запуска с нуля имеет некоторый адрес памяти, с которого они начинают выполняться. на ассемблере они называются как (вызовите "адрес памяти функции"). теперь возвращаемся к c если функция имеет адрес памяти, то они могут управляться указателями в c. так по правилам c</p><p>1. сначала нужно объявить указатель на функцию 2. передать адрес желаемой функции</p><p>**** примечание-> функции должны быть одного типа ****</p><p>эта простая программа проиллюстрирует каждую вещь.</p><pre><code>#include<stdio.h> void (*print)() ;//declare a function pointers void sayhello();//declare the function whose address is to be passed //the functions should be of same type int main() { print=sayhello;//addressof sayhello is assigned to print print();//print does a call to the function return 0; } void sayhello() { printf("\n hello world"); } </code></pre><p><img src="https://i.stack.imgur.com/l4qpj.png" alt="enter image description here">после этого давайте посмотрим, как машина понимает их. познакомимся с машинными инструкциями вышеупомянутой программы в 32-битной архитектуре.</p><p>область красной метки показывает, как происходит обмен и сохранение адреса в eax. тогда это инструкция вызова на eax. eax содержит желаемый адрес функции.</p>

<p>поскольку указатели на функции часто являются типизированными обратными вызовами, вы можете взглянуть на <a href="http://ccan.ozlabs.org/info/typesafe\_cb.html">введите безопасные обратные вызовы</a> . то же самое относится к точкам входа и т.д. функций, которые не являются обратными вызовами.</p><p>с довольно переменчивый и прощающий одновременно :)</p>