Указатели на функции

From Wiki.conus.info

Jump to: navigation, search

Указатель на функцию, в целом, как и любой другой указатель, просто адрес указывающий на начало функции в сегменте кода.

Это применяется часто в т.н. 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]
...
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox