How do function pointers in C work?

2018-12-31 00:02发布

I had some experience lately with function pointers in C.

So going on with the tradition of answering your own questions, I decided to make a small summary of the very basics, for those who need a quick dive-in to the subject.

12条回答
泪湿衣
2楼-- · 2018-12-31 00:10

The guide to getting fired: How to abuse function pointers in GCC on x86 machines by compiling your code by hand:

These string literals are bytes of 32-bit x86 machine code. 0xC3 is an x86 ret instruction.

You wouldn't normally write these by hand, you'd write in assembly language and then use an assembler like nasm to assemble it into a flat binary which you hexdump into a C string literal.

  1. Returns the current value on the EAX register

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Write a swap function

    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);
    
  3. Write a for-loop counter to 1000, calling some function each time

    ((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
    
  4. You can even write a recursive function that counts to 100

    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);
    

Note that compilers place string literals in the .rodata section (or .rdata on Windows), which is linked as part of the text segment (along with code for functions).

The text segment has Read+Exec permission, so casting string literals to function pointers works without needing mprotect() or VirtualProtect() system calls like you'd need for dynamically allocated memory. (Or gcc -z execstack links the program with stack + data segment + heap executable, as a quick hack.)


To disassemble these, you can compile this to put a label on the bytes, and use a disassembler.

// 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";

Compiling with gcc -c -m32 foo.c and disassembling with objdump -D -rwC -Mintel, we can get the assembly, and find out that this code violates the ABI by clobbering EBX (a call-preserved register) and is generally inefficient.

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

This machine code will (probably) work in 32-bit code on Windows, Linux, OS X, and so on: the default calling conventions on all those OSes pass args on the stack instead of more efficiently in registers. But EBX is call-preserved in all the normal calling conventions, so using it as a scratch register without saving/restoring it can easily make the caller crash.

查看更多
浮光初槿花落
3楼-- · 2018-12-31 00:11

Function pointers in C can be used to perform object-oriented programming in C.

For example, the following lines is written in C:

String s1 = newString();
s1->set(s1, "hello");

Yes, the -> and the lack of a new operator is a dead give away, but it sure seems to imply that we're setting the text of some String class to be "hello".

By using function pointers, it is possible to emulate methods in C.

How is this accomplished?

The String class is actually a struct with a bunch of function pointers which act as a way to simulate methods. The following is a partial declaration of the String class:

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

As can be seen, the methods of the String class are actually function pointers to the declared function. In preparing the instance of the String, the newString function is called in order to set up the function pointers to their respective functions:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

For example, the getString function that is called by invoking the get method is defined as the following:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

One thing that can be noticed is that there is no concept of an instance of an object and having methods that are actually a part of an object, so a "self object" must be passed in on each invocation. (And the internal is just a hidden struct which was omitted from the code listing earlier -- it is a way of performing information hiding, but that is not relevant to function pointers.)

So, rather than being able to do s1->set("hello");, one must pass in the object to perform the action on s1->set(s1, "hello").

With that minor explanation having to pass in a reference to yourself out of the way, we'll move to the next part, which is inheritance in C.

Let's say we want to make a subclass of String, say an ImmutableString. In order to make the string immutable, the set method will not be accessible, while maintaining access to get and length, and force the "constructor" to accept a char*:

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);

Basically, for all subclasses, the available methods are once again function pointers. This time, the declaration for the set method is not present, therefore, it cannot be called in a ImmutableString.

As for the implementation of the ImmutableString, the only relevant code is the "constructor" function, the newImmutableString:

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;
}

In instantiating the ImmutableString, the function pointers to the get and length methods actually refer to the String.get and String.length method, by going through the base variable which is an internally stored String object.

The use of a function pointer can achieve inheritance of a method from a superclass.

We can further continue to polymorphism in C.

If for example we wanted to change the behavior of the length method to return 0 all the time in the ImmutableString class for some reason, all that would have to be done is to:

  1. Add a function that is going to serve as the overriding length method.
  2. Go to the "constructor" and set the function pointer to the overriding length method.

Adding an overriding length method in ImmutableString may be performed by adding an lengthOverrideMethod:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

Then, the function pointer for the length method in the constructor is hooked up to the lengthOverrideMethod:

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;
}

Now, rather than having an identical behavior for the length method in ImmutableString class as the String class, now the length method will refer to the behavior defined in the lengthOverrideMethod function.

I must add a disclaimer that I am still learning how to write with an object-oriented programming style in C, so there probably are points that I didn't explain well, or may just be off mark in terms of how best to implement OOP in C. But my purpose was to try to illustrate one of many uses of function pointers.

For more information on how to perform object-oriented programming in C, please refer to the following questions:

查看更多
像晚风撩人
4楼-- · 2018-12-31 00:14

Function pointer is usually defined by typedef, and used as param & return value,

Above answers already explained a lot, I just give a full example:

#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 &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // 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;
}
查看更多
与风俱净
5楼-- · 2018-12-31 00:14

One of the big uses for function pointers in C is to call a function selected at run-time. For example, the C run-time library has two routines, qsort and bsearch, which take a pointer to a function that is called to compare two items being sorted; this allows you to sort or search, respectively, anything, based on any criteria you wish to use.

A very basic example, if there is one function called print(int x, int y) which in turn may require to call add() function or sub() which are of similar types then what we will do, we will add one function pointer argument to the print() function as shown below:-

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is : %d", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}
查看更多
无色无味的生活
6楼-- · 2018-12-31 00:18

Since function pointers are often typed callbacks, you might want to have a look at type safe callbacks. The same applies to entry points, etc of functions that are not callbacks.

C is quite fickle and forgiving at the same time :)

查看更多
墨雨无痕
7楼-- · 2018-12-31 00:19

Function pointers become easy to declare once you have the basic declarators:

  • id: ID: ID is a
  • Pointer: *D: D pointer to
  • Function: D(<parameters>): D function taking <parameters> returning

While D is another declarator built using those same rules. In the end, somewhere, it ends with ID (see below for an example), which is the name of the declared entity. Let's try to build a function taking a pointer to a function taking nothing and returning int, and returning a pointer to a function taking a char and returning int. With type-defs it's like this

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

As you see, it's pretty easy to build it up using typedefs. Without typedefs, it's not hard either with the above declarator rules, applied consistently. As you see i missed out the part the pointer points to, and the thing the function returns. That's what appears at the very left of the declaration, and is not of interest: It's added at the end if one built up the declarator already. Let's do that. Building it up consistently, first wordy - showing the structure using [ and ]:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

As you see, one can describe a type completely by appending declarators one after each other. Construction can be done in two ways. One is bottom-up, starting with the very right thing (leaves) and working the way through up to the identifier. The other way is top-down, starting at the identifier, working the way down to the leaves. I'll show both ways.

Bottom Up

Construction starts with the thing at the right: The thing returned, which is the function taking char. To keep the declarators distinct, i'm going to number them:

D1(char);

Inserted the char parameter directly, since it's trivial. Adding a pointer to declarator by replacing D1 by *D2. Note that we have to wrap parentheses around *D2. That can be known by looking up the precedence of the *-operator and the function-call operator (). Without our parentheses, the compiler would read it as *(D2(char p)). But that would not be a plain replace of D1 by *D2 anymore, of course. Parentheses are always allowed around declarators. So you don't make anything wrong if you add too much of them, actually.

(*D2)(char);

Return type is complete! Now, let's replace D2 by the function declarator function taking <parameters> returning, which is D3(<parameters>) which we are at now.

(*D3(<parameters>))(char)

Note that no parentheses are needed, since we want D3 to be a function-declarator and not a pointer declarator this time. Great, only thing left is the parameters for it. The parameter is done exactly the same as we've done the return type, just with char replaced by void. So i'll copy it:

(*D3(   (*ID1)(void)))(char)

I've replaced D2 by ID1, since we are finished with that parameter (it's already a pointer to a function - no need for another declarator). ID1 will be the name of the parameter. Now, i told above at the end one adds the type which all those declarator modify - the one appearing at the very left of every declaration. For functions, that becomes the return type. For pointers the pointed to type etc... It's interesting when written down the type, it will appear in the opposite order, at the very right :) Anyway, substituting it yields the complete declaration. Both times int of course.

int (*ID0(int (*ID1)(void)))(char)

I've called the identifier of the function ID0 in that example.

Top Down

This starts at the identifier at the very left in the description of the type, wrapping that declarator as we walk our way through the right. Start with function taking <parameters> returning

ID0(<parameters>)

The next thing in the description (after "returning") was pointer to. Let's incorporate it:

*ID0(<parameters>)

Then the next thing was functon taking <parameters> returning. The parameter is a simple char, so we put it in right away again, since it's really trivial.

(*ID0(<parameters>))(char)

Note the parentheses we added, since we again want that the * binds first, and then the (char). Otherwise it would read function taking <parameters> returning function .... Noes, functions returning functions aren't even allowed.

Now we just need to put <parameters>. I will show a short version of the deriveration, since i think you already by now have the idea how to do it.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Just put int before the declarators like we did with bottom-up, and we are finished

int (*ID0(int (*ID1)(void)))(char)

The nice thing

Is bottom-up or top-down better? I'm used to bottom-up, but some people may be more comfortable with top-down. It's a matter of taste i think. Incidentally, if you apply all the operators in that declaration, you will end up getting an int:

int v = (*ID0(some_function_pointer))(some_char);

That is a nice property of declarations in C: The declaration asserts that if those operators are used in an expression using the identifier, then it yields the type on the very left. It's like that for arrays too.

Hope you liked this little tutorial! Now we can link to this when people wonder about the strange declaration syntax of functions. I tried to put as little C internals as possible. Feel free to edit/fix things in it.

查看更多
登录 后发表回答