You can tell C/C++ about a whole group of functions that take the same arguments and return types, and then "point" to one of those functions. This is called a "function pointer", and in assembly it's just the address of the code to run. The ugliest part about function pointers in C/C++ (by far!) is the syntax--I recommend you always use a typedef to define a function pointer. For example, here's how you make a new type "funPtr" that takes a short and returns an int:
typedef int (*funPtr)(short param);
Now you can declare variables of type "funPtr", assign compatible functions into them, and finally call them.
int someFun(short param) { return param+3; } typedef int (*funPtr)(short param); long foo(void) { funPtr f; // f is a function pointer, of type "funPtr" f=someFun; // f points to existing function return f(10); // call existing function }
In assembly, a function pointer is just a normal pointer. You call the function with "call" as usual.
One common use of function pointers is to fake C++-style "class methods" by putting a function inside a C struct. Here's a simple example of this technique:
void response_OK() { printf("I got this!\n"); } struct student { void (*response)(); // pointer to function capturing student's behavior }; void foo() { struct student s; s.response=response_OK; // set up method s.response(); // run response "method" }
Since this function takes no parameters, the function can't tell anything about the struct it's running from. It's common to pass the struct itself as a parameter to the function, which lets the function use fields of the struct.
struct student { int stress; // current stress level void (*response)(struct student *self); // response method }; void response_check(struct student *self) { if (self->stress<100) printf("I got this!\n"); else printf("ARGH!\n"); } void foo() { struct student s; s.stress=30; s.response=response_check; // set up method s.response(&s); // run response "method" }
In a complicated C program, like an operating system kernel, there can be many data fields and function fields working together in a single struct. This design idiom was used to create C++'s classes.
In assembly, a function pointer inside a struct is just another field. You can use the offset from the start of the struct to extract the function pointer, and then call it normally.
mov rdi,structPtr call QWORD[rdi+8] ; call responseFn ret responseFn: ; this function acts like a method mov rdi,OKstr extern puts call puts ret OKstr: db `A-OK!`,0 section .data structPtr: dq 30 ; offset 0: student stress level dq responseFn ; offset 8: student response function
In C++, to save space all the virtual methods sit together in a separate structure called the "vtable", or virtual method table. This means you can add virtual methods, and the single shared vtable gets bigger, but each instance of the class stays the same size.
Here's how the C++ vtable could be written in plain C:
struct parent; typedef void (*fn_parent_bar)(struct parent *this); struct parent_vtable { /* "virtual method table", listing function pointers to all virtual methods */ fn_parent_bar bar; }; struct parent { const struct parent_vtable *vtable; int v; }; void parent_bar(struct parent *this) { printf("I'm the parent (v=%d)\n",this->v); } void child_bar(struct parent *this) { printf("I'm the child (v=%d)\n",this->v); } int foo(void) { struct parent_vtable parent_class; /* set up parent vtable */ parent_class.bar=parent_bar; /* set up parent's function pointer */ struct parent_vtable child_class; child_class.bar=child_bar; struct parent p; p.vtable=&parent_class; p.v=10; p.vtable->bar(&p); /* call function pointer */ struct parent c; /* "child class", with different methods */ c.vtable=&child_class; c.v=11; c.vtable->bar(&c); return 0; }
The only difference between C++ virtual methods and the above "struct field is a function pointer" trick are:
"sizeof" will actually show you the space used by the "vtable" pointer in a C++ class with virtual methods--a class with virtual methods is 8 bytes bigger than a class without virtual methods, to leave space for the vtable.
There are many examples of using function pointers as methods inside structs in large C programs, such as the Linux kernel. For example:
Often the function pointers are corralled into a separate read-only struct named "ops" or "operations", which like the C++ vtable is there to save memory when there are many operations.
CS 301 Lecture Note, 2014, Dr. Orion Lawlor, UAF Computer Science Department.