Why the Size of a Pointer to a Function Is Different from the Size of a Pointer to a Member Function

Why the size of a pointer to a function is different from the size of a pointer to a member function?

In the most normal situation, you can pretty much think of

struct A {
int i;
int foo() { return i; }
};

A a;
a.foo();

as

struct A {
int i;
};
int A_foo( A* this ) { return this->i; };

A a;
A_foo(&a);

(Starting to look like C, right?) So you would think the pointer &A::foo would just be the same as a normal function pointer. But there are a couple of complications: Multiple inheritance, and virtual functions.

So imagine we have:

struct A {int a;};
struct B {int b;};
struct C : A, B {int c;};

It might be laid out like this:

Multiple inheritance

As you can see, if you want to point to the object with an A* or a C*, you point to the start, but if you want to point to it with a B* you have to point somewhere in the middle.

So if C inherits some member function from B and you want to point to it then call the function on a C*, it needs to know to shuffle the this pointer. That information needs to be stored somewhere. So it gets lumped in with the function pointer.

Now for every class that has virtual functions, the compiler creates a list of them called a virtual table. It then adds an extra pointer to this table to the class (vptr). So for this class structure:

struct A
{
int a;
virtual void foo(){};
};
struct B : A
{
int b;
virtual void foo(){};
virtual void bar(){};
};

The compiler might end up making it like this:
Sample Image

So a member function pointer to a virtual function actually needs to be an index into the virtual table.
So a member function pointer actually needs 1) possibly a function pointer, 2) possibly an adjustment of the this pointer, and 3) possibly a vtable index. To be consistent, every member function pointer needs to be capable of all of these. So that's 8 bytes for the pointer, 4 bytes for the adjustment, 4 bytes for the index, for 16 bytes total.

I believe this is something that actually varies a lot between compilers, and there are a lot of possible optimizations. Probably none actually implements it the way I've described.

For a lot of detail, see this (scroll to "Implementations of Member Function Pointers").

Size of pointer to member function varies like crazy

See here for docs page for /vm options

If you use the '/vmg' compiler option then pointer-to-member function will always be 16 bytes as you're effectively telling the compiler that it may not know the size beforehand and so has to assume the worst (virtual inheritance!).

If you use '/vmb' then the compiler must know about the inheritance pattern for the struct before use and so can use the most efficient method - in the case of simple inheritance this is 4 bytes.

Its likely that in some projects you've got '/vmg' set (which makes the class 16 bytes) and in others you dont (which makes the class 4 bytes).

/vmb is the implicit default - check your compiler command line for the libraries where this class is 16 bytes for /vmg

About sizeof of a class member function pointer

My question is why sizeof(a_func_ptr) returns 16, while sizeof(func_ptr) returns 4 (as for any pointer on x86 system)?

Because pointer-to-members are implemented differently. They're not pointers under the hood. Some compilers, such as MSVC, implement them as struct with more than one members in it.

Read this interesting article:

  • Pointers to member functions are very strange animals

Note that in some compilers, they might have same size. The bottomline is: they're compiler-dependent.

How is a pointer to a member function different than a pointer to a data member?

Before, I was thinking that a pointer to a member function is completely different to a pointer to a data member.

You've thought correctly.

How do pointers to member functions differ from pointers to data members?

They are separate types. They may have different sizes. Pointer to a member function can point to a member function. Pointer to a data member can point to a data member. The difference is analogous to the one between function pointers and data pointers.

make_member_ptr<int(char, long), class_type> is a pointer to member function.

Are all non-member function pointers the same size in C++

Yes, quoting from expr.reinterpret.cast/6:

A function pointer can be explicitly converted to a function pointer of a different type.

[Note 5: The effect of calling a function through a pointer to a function type ([dcl.fct]) that is not the same as the type used in the definition of the function is undefined ([expr.call]). — end note]

Except that converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.

[ EDIT ]   The "yes" part of the answer refers to OP's question as worded in the body of the post: "does C++ provide the same guarantee that C does that any (non-member) function pointer may hold the value of any other (non-member) function pointer?".

As pointed out in @NateEldredge's comments, this does not automatically imply that "all non-member function pointers [are] the same size in C++" (as the title of the question reads), though it would strongly suggest that they do.

Can the size of pointers vary between data and function pointers?

> type ppp.c
#include <stdio.h>
#include <stdlib.h>

int global = 0;

int main(void) {
int local = 0;
static int staticint = 0;
int *mall;
int (*fx)(void);

fx = main;
mall = malloc(42); /* assume it worked */
printf("#sizeof pointer to local: %d\n", (int)sizeof &local);
printf("#sizeof pointer to static: %d\n", (int)sizeof &staticint);
printf("#sizeof pointer to malloc'd: %d\n", (int)sizeof mall);
printf("#sizeof pointer to global: %d\n", (int)sizeof &global);
printf("#sizeof pointer to main(): %d\n", (int)sizeof fx);
free(mall);
return 0;
}
> tcc -mc ppp.c
Turbo C Version 2.01 ...
warnings about unused variables elided ...
Turbo Link Version 2.0 ...
> ppp
#sizeof pointer to local: 4
#sizeof pointer to static: 4
#sizeof pointer to malloc'd: 4
#sizeof pointer to global: 4
#sizeof pointer to main(): 2
> tcc -mm ppp.c
> ppp
#sizeof pointer to local: 2
#sizeof pointer to static: 2
#sizeof pointer to malloc'd: 2
#sizeof pointer to global: 2
#sizeof pointer to main(): 4

tcc -mc generates code in the "compact" model; tcc -mm generates code in the "medium" model

What is guaranteed about the size of a function pointer?

From C99 spec, section 6.2.5, paragraph 27:

A pointer to void shall have the same
representation and alignment
requirements as a pointer to a
character type. Similarly, pointers
to qualified or unqualified versions of
compatible types shall have the same
representation and alignment
requirements. All pointers to
structure types shall have the same
representation and alignment
requirements as each other. All
pointers to union types shall have the
same representation and alignment
requirements as each other. Pointers
to other types need not have the same
representation or alignment
requirements.

So no; no guarantee that a void * can hold a function pointer.

And section 6.3.2.3, paragraph 8:

A pointer to a function of one type
may be converted to a pointer to a
function of another type and back
again; the result shall compare equal
to the original pointer.

implying that one function pointer type can hold any other function pointer value. Technically, that's not the same as guaranteeing that function-pointer types can't vary in size, merely that their values occupy the same range as each other.

sizeof Pointer differs for data type on same architecture

The C standard lays down the law on what's required:

  • All data pointers can be converted to void* and back without loss of information.
  • All struct-pointers have the same representation+alignment and can thus be converted to each other.
  • All union-pointers have the same representation+alignment and can thus be converted to each other.
  • All character pointers and void pointers have the same representation+alignment.
  • All pointers to qualified and unqualified compatible types shall have the same representation+alignment. (For example unsigned / signed versions of the same type are compatible)

  • All function pointers have the same representation+alignment and can be converted to any other function pointer type and back again.

Nothing more is required.

The committee arrived at these guarantees by examining all current implementations and machines and codifying as many guarantees as they could.

On architectures where pointers are naturally word pointers instead of character pointers, you get data pointers of different sizes.

On architectures with different size code / data spaces (many micro-processors), or where additional info is needed for properly invoking functions (like itanium, though they often hide that behind a data-pointer), you get code pointers of different size from data pointers.



Related Topics



Leave a reply



Submit