I don't get a warning when I import a void pointer into a structure that has a function pointer in a shared object

Yesterday, I did some research on dynamically loading shared objects and getting function pointers.

I have been told many times that sharing pointers to functions via void pointers is prohibited by the ISO C ++ standard and still remains a problem.

After reading the Johan Pettersons artitle "about the problem with dlsym" I better understand the reasons, and I also understand that this is prohibited by the standard does not mean that you absolutely should not use it. Otherwise, how do all C ++ programmers work with functions from common objects with the correct ISO C ++ code? Just guessing, I could be wrong, I am not very good at C ++.

During an experiment with my code, I found that by sharing a pointer to a structure that contains a link to the function I want to call, my compiler will not complain. I use -Wall and -pedantic when compiling.

My code is as follows:

myclass.hpp

 class myclass { public: virtual void dosomething (void)=0; } 

api.hpp

 #include <myclass.hpp> struct API { myclass* (* func)(void); }; 

so.hpp

 #include <iostream> #include "myclass.cpp" #include "api.hpp" class childclass : public myclass { void dosomething (void) { std::cout << "Did it.\n"; } } /* function to return a new instance of childclass */ extern "C" myclass* make (void) { return new childclass; } /* struct that contains a pointer to the function */ extern "C" API interface; API interface { make }; 

host.cpp

 #include <iostream> #include <dlfcn.h> #include "myclass.hpp" #include "api.hpp" int main (void) { void * th = dlopen("./so.so", RTLD_LAZY); /* error checking was here */ #ifndef usefunction API* api = static_cast<API*>( dlsym(th, "interface") ); myclass * inst = api->make(); inst->dosomething(); #else myclass* (*func)(void) = reinterpret_cast<myclass* (*)(void)>( dlsym(th, "make") ); /* will never get to this point */ #endif return 0; } 

so.so compiled so.so , I will then compile the host.cpp file.

g++ -ldl -Wall -pedantic host.cpp -o host Compiles in order, the program prints Did it. correctly Did it. at startup.

g++ -ldl -Wall -pedantic host.cpp -o host -Dusefunction Complains

 In function 'int main(int, char**)': warning: ISO C++ forbids casting between pointer-to-function and pointer-to-object [enabled by default] 

I know that this is just a warning, but why is it not a warning in the first case when using the structure, if at the end I can indirectly refer to a pointer to a function that is in the shared object

Talking about this, does anyone know a way to achieve all this in the absolutely correct form of ISO C ++? Does it even exist?

+7
source share
4 answers

If you have a pointer to a function wrapped in a structure, then you are faced with a problem, adding an additional level of indirection. Imagine if a function pointer took 10 bytes, but an object pointer took four bytes. Your structure will be at least 10 bytes, and you will have a pointer to 4 bytes. All this is wonderful. When you access the structure to go to the function pointer, you pull out all 10 bytes. Nothing is lost. However, if you pointed a 10-byte function pointer to a 4-byte object pointer, you would certainly lose 6 bytes of information.

There is no real problem, since platforms supporting dlsym should have void pointers that are large enough to hold the address of the function pointer, but the compiler is trying to prevent you from writing non-portable code.

+1
source

Completely standard appropriate solution:

 extern "C" typedef int (func_t)(char, double); // desired API function signature int main() { static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible"); void * p = dlsym(handle, "magic_function"); char const * cp = reinterpret_cast<char const *>(&p); func_t * fp; std::copy(cp, cp + sizeof p, reinterpret_cast<char *>(&fp)); return fp('a', 1.25); } 

One simpler, albeit more dubious way of writing it, uses a punning bit:

 static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible"); void * vp = dlsym(handle, "magic_function"); func_t * fp; *reinterpret_cast<void **>(&fp) = vp; // this is type-punning 
+2
source

First, g ++ does not consider communication as part of this type. You so.hpp should not compile since it initializes a myclass* (*)() (pointer to the extern "C" function) with make , which is the extern "C" function. This is illegal and requires a compiler error, but g ++ accepts it even without warning.

Also, why host.cpp generates a warning if usefunction not defined. In a DLL, an interface is an instance of a data type that contains a pointer to a function. You use dlsym to get the address of this variable (not the function) that you passed into the data type. Under no circumstances will you convert a data pointer to a function pointer; you are looking for a pointer to a data object that contains a pointer to a function that is in order.

As for the version with reinterpret_cast , the warning is justified: dlsym returns a pointer to a function (not a variable), but returns it as void* . The standard (at least through C ++ 03) says that this conversion is illegal, and I used compilers where it is impossible to make it work, because pointers to functions were more than pointers to data. As a limitation of what it allows in C, Unix (Posix) requires pointers to functions and pointers so that the data has the same size and presentation, and the Posix standard says to convert the dlsym return value as follows:

 myclass* (*func)(); *reinterpret_cast<void**>( &func ) = dlsym( th, "make" ); 

If a myclass* (*)() and a void* really have the same size and representation, this is legal and will work (and should not trigger any warning).

+2
source

The warning from gcc is because gcc does not know whether you agree to assume Posix or not. Thus, it does not accept and (as required by the C ++ standard), it diagnoses a poorly formed program.

However, you use dlsym and expect it to do what Posix dlsym does, so you are ready to rely on Posix. You can then do C-style from void* to your type of function pointer, and gcc ensures that this is normal, even if C ++ does not. Any system without Posix that mimics dlsym would have to guarantee something similar, since otherwise it would be pointless to return a function pointer to void* .

Since you know what you are doing, you can disable any warnings from gcc.

The reason your code with the API structure does not give any warnings is because void* can be static_cast any pointer to an object. I think that you violate a strict alias when accessing the data element, because you are referencing memory through an API type lvalue and a function pointer type lvalue when the actual object in this place is void* , but since the layout of your structure is the same as void* layout and function pointer in your implementation, it still works. Theoretically, even with the same layouts, it can break due to severe violation of aliases (more likely, the more optimization you use).

A safe way to avoid severe aliases is std::memcpy(&fp, &p, sizeof p) : the same as Kerrek std::copy , but with less reinterpret_casts cluttering up the space because memcpy takes up void* , whereas std::copy requires a full type. Also, avoiding the strict violation of aliases, it is also convenient to avoid any diagnostics. You no longer perform functions between pointers of objects and objects: you directly copy the representation of the object to another. Provided that the void* object representation is guaranteed to be the same as a function pointer (which is the case in Posix), this will work.

+2
source

All Articles