Saturday, July 16, 2005

Casting cv-qualifier and function pointers to void*

Okay..so this is my first post to this blog of mine. This post is a result of a very fine discussion that I had with some of the Gurus at http://www.codeguru.com. I thought I would compile the stuff from that and present it here.

Before I proceed any further I would like to thank the members of Codeguru who participated with keen interest and helped me get the concept right. Thanks to - Graham, SuperKoko, Smasher/Devourer, Wildfrog, NoHero and Improving.

The question was - Can void pointers point to const, volatile and function pointers? Explain.

The answer is - Quotes from MSDN:
"If a pointer's type is void *, the pointer can point to any variable that is not declared with the const or volatile keyword."
AND
"A void pointer can point to a function, but not to a class member in C++."

Thats pretty much it but lets go through the explanation to get to the basic reasons why MSDN negates the possibilities.

const and volatile are collective named as cv-qualifiers by the standard and a basic principle is that we are not allowed to lessen the amount of cv-qualification of an object. If we were to assign a cv-qualified object to a void pointer, then we could cast them back to a non-cv-qualified pointer to the original object. This steals away the whole essence of the cv-qualification of the objects while the intention of the standard is that only const_cast can remove cv-qualification from an object.

The standard treats const and volatile identically as far as their syntax and restrictions are concerned. The standard intentionally makes it hard for us to lose the cv-qualification of an object, since this is almost always an error in coding, not that its impossible.

You could use this as an example:
[CODE]
volatile int arg = 0;
void * ptr1 = &arg; // Valid but it won't be volatile anymore

A better way to do this could be:
[CODE]
volatile int arg = 0;
volatile void * ptr1 = &arg;

Okay, so that's for the CV-qualifiers. Now lets get back on track with the case of function (class-member functions as well as non-member functions) pointers.

Let's take an example:
[Code]
#include<iostream>
using namespace std;

//This creates a typedef called IntFuncPtr which is a pointer to a function
// that takes no arguments and returns an int.
typedef int (*IntFuncPtr)();

// An example class with a single member function.
class A {
public: int f() { return 1; }
};

//Note this function has the proper signature to be addressed by an IntFuncPtr.
int g() { return 2; }

int main() {
void *pVoid;

pVoid = g; //OK: cast from int (*)() to void*
cout << ((IntFuncPtr) pVoid) () << endl;
pVoid = A::f; //ERROR: can't cast from int (A::*)() to void*
return 0;
}

In this program, Visual C++ 6.0 will allow to make an implicit conversion from a (non-class member) function pointer to a void pointer, in the line pVoid = g. In the following line, we cast that pointer back to its appropriate type and use it to call the function it points to, to demonstrate that it holds the correct value. The line after that, attempting to assign a class member function pointer to a void pointer, does not work.

So, we see that we can use void* to point to non-member functions but not member functions. Is this really true?? Even if we are able to get the non-member functions part going, is it portable??? Comeau compiler is considered to follow the C++ standards to the closest and running this code there did not allow what VC++ allowed easily. It gave the following error message - "A value of type int (*)() cannot be assigned to an entity of type void*."

The reason for this is well explained by Herb Sutter in his More Exceptional C++. He says : "Although a void* is guaranteed to be big enough to hold the value of any object pointer, it is not guaranteed to be suitable to hold a function pointer; for example, on some platforms a function pointer is larger than an object pointer." That is, in general, for a member function pointer or a non-member function pointer.

Here's an example that illustrates this for the case of member function pointers:

[CODE]
#include<iostream>
using namespace std;

class A
{
public:virtual void f();
};

class B
{
public:int x;
};

class C : public B, public A
{
};

int f()
{
return 0;
}

int main()
{
void *p=reinterpret_cast<void *>(f);
cout << sizeof(&A::f) << " "<< sizeof(static_cast<void (C::*)()>(&A::f)) << endl;
return 0;
}

Outputs:
8 8 (With GCC)
12 12 (With BC++)
4 8 (With VC++)

While sizeof(void*) will gives you 4, with above.

You might get better affirmative results for classes with simple inheritance but with multiple inheritance (the case in the program), virtual inheritance, virtual functions and in some more complex scenarios, you could get the size of the function pointers greater than sizeof(void*), may be 4, 8 and sometimes 12 or even greater. They are very compiler dependent and class dependent.

You can't even assign a pointer to non-member functions to a void* for the same reasons (even if some compiler would allow you doing so), they are not guarenteed to be of the same size. These are however relatively portable than the case of member-function pointers and that is why MSDN says you can (for non-member functions) atleast on MS platform. But, you know that's not portable. A work-around could be using reinterpret_cast to cast a function pointer to another function pointer type (say, any one that you can use throughout your code base for consistency) and then cast it back to it's original type. The standard guarantees that to work. But the drawback is that you lose type-safety, which itself is not a very good thing to do in ideal scenarios.

Further reading and references: online Comeau compiler at Comeau online , Recursive Declaration.

Cheers!!!

5 comments:

Anonymous said...

int main()
{
void *p=reinterpret_cast(f);
cout<(&A::f))<
return 0;
}

Hi,is something missed? How can you get an output like as "8 8"?

abnegator said...

anonymous.. - yes you are right.. seems the blogger doesn't like the << etc. It galloped some code! I will try fixing it..

Anonymous said...

Thanks for that.

Anonymous said...

When you posted 'CV', I thought you meant curriculum vitae! Silly me. You see I'm trying to write resumes for a living.

intranet software said...

Thanks for sharing this one. Im not really good at pointers expecially if its C++ language or C. Im trying to understand the concept of pointers and i found this blog. Im enlightened!