Указатели на функции
From Wiki.conus.info
Указатель на функцию, в целом, как и любой другой указатель, просто адрес указывающий на начало функции в сегменте кода.
Это применяется часто в т.н. callback-ах.
Известные примеры: qsort из стандартной библиотеки Си, сигналы в юниксах, фукнция atexit(), создание тредов (CreateThread, pthread_create), а еще это часто применяется в win32, например: EnumChildWindows.
Итак, функция qsort() это реализация алгоритма "быстрой сортировки". Функция может вполне сортировать что угодно, любые типы данных, но при условии что вы напишите функцию сравнения двух элементов данных и qsort() сможет вызывать её.
Эта функция сравнения может определяться так:
int (*compare)(const void *, const void *)
Воспользуемся немного модифицированным примером, который я нашел вот здесь:
/* ex3 Sorting ints with qsort */ // #include <stdio.h> #include <stdlib.h> int comp(const void * _a, const void * _b) { const int *a=(const int *)_a; const int *b=(const int *)_b; if (*a==*b) return 0; else if (*a < *b) return -1; else return 1; } int main(int argc, char* argv[]) { int numbers[10]={1892,45,200,-98,4087,5,-12345,1087,88,-100000}; int i; /* Sort the array */ qsort(numbers,10,sizeof(int),comp) ; for (i=0;i<9;i++) printf("Number = %d\n",numbers[ i ]) ; return 0; }
Компилируем в MSVC 2010 (я убрал некоторые части для краткости) с опцией /Ox:
__a$ = 8 ; size = 4 __b$ = 12 ; size = 4 _comp PROC mov eax, DWORD PTR __a$[esp-4] mov ecx, DWORD PTR __b$[esp-4] mov eax, DWORD PTR [eax] mov ecx, DWORD PTR [ecx] cmp eax, ecx jne SHORT $LN4@comp xor eax, eax ret 0 $LN4@comp: xor edx, edx cmp eax, ecx setge dl lea eax, DWORD PTR [edx+edx-1] ret 0 _comp ENDP ... _numbers$ = -44 ; size = 40 _i$ = -4 ; size = 4 _argc$ = 8 ; size = 4 _argv$ = 12 ; size = 4 _main PROC ; Line 19 push ebp mov ebp, esp sub esp, 44 ; 0000002cH ; Line 20 mov DWORD PTR _numbers$[ebp], 1892 ; 00000764H mov DWORD PTR _numbers$[ebp+4], 45 ; 0000002dH mov DWORD PTR _numbers$[ebp+8], 200 ; 000000c8H mov DWORD PTR _numbers$[ebp+12], -98 ; ffffff9eH mov DWORD PTR _numbers$[ebp+16], 4087 ; 00000ff7H mov DWORD PTR _numbers$[ebp+20], 5 mov DWORD PTR _numbers$[ebp+24], -12345 ; ffffcfc7H mov DWORD PTR _numbers$[ebp+28], 1087 ; 0000043fH mov DWORD PTR _numbers$[ebp+32], 88 ; 00000058H mov DWORD PTR _numbers$[ebp+36], -100000 ; fffe7960H ; Line 24 push OFFSET _comp push 4 push 10 ; 0000000aH lea eax, DWORD PTR _numbers$[ebp] push eax call _qsort add esp, 16 ; 00000010H ...
Ничего особо удивительного здесь мы не видим. В качестве четвертого аргумента, в qsort просто передается адрес метки _comp, где собственно и располагается функция comp().
Как qsort() вызывает её?
Посмотрим в MSVCR80.DLL (эта DLL куда в MSVC вынесли функции из стандартных библиотек Си):
.text:7816CBF0 ; void __cdecl qsort(void *, unsigned int, unsigned int, int (__cdecl *)(const void *, const void *)) .text:7816CBF0 public _qsort .text:7816CBF0 _qsort proc near .text:7816CBF0 .text:7816CBF0 lo = dword ptr -104h .text:7816CBF0 hi = dword ptr -100h .text:7816CBF0 var_FC = dword ptr -0FCh .text:7816CBF0 stkptr = dword ptr -0F8h .text:7816CBF0 lostk = dword ptr -0F4h .text:7816CBF0 histk = dword ptr -7Ch .text:7816CBF0 base = dword ptr 4 .text:7816CBF0 num = dword ptr 8 .text:7816CBF0 width = dword ptr 0Ch .text:7816CBF0 comp = dword ptr 10h .text:7816CBF0 .text:7816CBF0 sub esp, 100h .... .text:7816CCE0 loc_7816CCE0: ; CODE XREF: _qsort+B1�j .text:7816CCE0 shr eax, 1 .text:7816CCE2 imul eax, ebp .text:7816CCE5 add eax, ebx .text:7816CCE7 mov edi, eax .text:7816CCE9 push edi .text:7816CCEA push ebx .text:7816CCEB call [esp+118h+comp] .text:7816CCF2 add esp, 8 .text:7816CCF5 test eax, eax .text:7816CCF7 jle short loc_7816CD04
comp - это четвертый аргумент функции. Здесь просто передается управление по адресу указанному в comp. При этом подготавливается два аргумента для функции. Далее, проверяется результат её выполнения.
Вот почему использование указателей на функции - это опасно. Во-первых, если вызвать qsort() с неправильным указателем на функцию, то qsort(), дойдя до этого вызова, может передать управление неизвестно куда, процесс свалится, и эту ошибку можно будет найти не сразу.
Во-вторых, типизация должна соблюдаться, вызов не той функции с не теми аргументами не того типа, может привести к плачевным результатам, хотя это и не проблема, а проблема это найти ошибку. Ведь компилятор может вас и не предупредить о потенциальных неприятностях.
GCC
Не слишком большая разница:
lea eax, [esp+40h+var_28] mov [esp+40h+var_40], eax mov [esp+40h+var_28], 764h mov [esp+40h+var_24], 2Dh mov [esp+40h+var_20], 0C8h mov [esp+40h+var_1C], 0FFFFFF9Eh mov [esp+40h+var_18], 0FF7h mov [esp+40h+var_14], 5 mov [esp+40h+var_10], 0FFFFCFC7h mov [esp+40h+var_C], 43Fh mov [esp+40h+var_8], 58h mov [esp+40h+var_4], 0FFFE7960h mov [esp+40h+var_34], offset comp mov [esp+40h+var_38], 4 mov [esp+40h+var_3C], 0Ah call _qsort
Функция comp():
public comp comp proc near arg_0 = dword ptr 8 arg_4 = dword ptr 0Ch push ebp mov ebp, esp mov eax, [ebp+arg_4] mov ecx, [ebp+arg_0] mov edx, [eax] xor eax, eax cmp [ecx], edx jnz short loc_8048458 pop ebp retn loc_8048458: setnl al movzx eax, al lea eax, [eax+eax-1] pop ebp retn comp endp
Имплементация qsort() находится в libc.so.6, и представляет собой просто враппер для qsort_r().
Она, в свою очередь, вызывает quicksort(), где есть вызовы определенной нами функции через указатель:
(файл libc.so.6, версия glibc - 2.10.1)
.text:0002DDF6 mov edx, [ebp+arg_10] .text:0002DDF9 mov [esp+4], esi .text:0002DDFD mov [esp], edi .text:0002DE00 mov [esp+8], edx .text:0002DE04 call [ebp+arg_C] ...