The following code is from an existing application that has to be compiled in both C and C++. There's a macro:
/* Type-checking macro to provide arguments for CoCreateInstance() etc.
* The pointer arithmetic is a compile-time pointer type check that 'obj'
* really is a 'type **', but is intended to have no effect at runtime. */
#define COMPTR(type, obj) &IID_##type, \
(void **)(void *)((obj) + (sizeof((obj)-(type **)(obj))) \
- (sizeof((obj)-(type **)(obj))))开发者_Go百科
that is used as follows:
ISomeInterface *object;
CoCreateInstance(&CLSID_SomeInterfaceImpl, NULL,
CLSCTX_INPROC_SERVER, COMPTR(ISomeInterface, &object))));
here the idea is that the last two parameters of CoCreateInstance()
are IID&
and void**
and that macro grabs ISomeInterface**
and converts it to IID&
and void**
at the same time enforcing a compile-time check that the address passed in place of ISomeInterface**
is indeed an address of ISomeInterface*
pointer variable.
Okay, but what's the need for
((obj) + (sizeof((obj)-(type **)(obj))) \
- (sizeof((obj)-(type **)(obj)))
complex expression? I see that the type checking is enforced with (obj)-(type**)(obj)
subexpression. What's the need for adding and then subtracting the sizeof()
? And what's the need for casting to void*
before casting to void**
?
I suppose the same can be done as follows:
#define COMPTR(type, obj) &IID_##type, \
(void **)(sizeof((obj)-(type**)(obj)), obj)
here the first part of comma operator would contain a sizeof()
that would enforce the typecheck and evaluate to a constant, the second part would just yield the same pointer and the pointer would be cast to void**
.
What can the original macro do what the one I suggest can't? What's the need for those complications?
Maybe the original author wasn't aware of the comma operator? This is not exactly unheard of among C/C++ programmers.
Maybe the original author wasn't aware of function templates. This macro is begging to be replaced by a function template.
Apparently the fourth argument to CoCreateInstance
is a pointer to some global object of type IID that pertains to the type
(type argument to COMPTR) at hand. The fifth and final argument to CoCreateInstance
is supposed to be a type**
pointer.
Instead, the function CoCreateInstance
takes a void**
(yech!) pointer as its last argument, obtained by casting the supposed type**
pointer. The cast goes through void*
as an intermediary because any pointer can be cast to/from a void* pointer.
Sans the protection in that COMPTR macro, one could pass a double*
pointer, or even a long long
(not a pointer!) as the fifth argument to CoCreateInstance
. Of course, this whole mess would have been avoided had the original author used C++ what its very good at, type safety. Instead he/she decided to go the void* pointer route and put the protection in the macro.
What the goofiness is doing: The argument to sizeof
is the pointer difference expression (obj)-(type**)(obj)
. If obj
is a type**
pointer, this is 0 (as type ptrdiff_t). If obj
is something else, this pointer difference expression is ill-formed. So, two cases, obj
is a type**
pointer, or it isn't.
Case 1, obj
is a type**
pointer: The pointer difference expression is valid, so the last argument to CoCreateInstance
expands to (void**)(void*)(obj+8-8)
, assuming a 64 bit machine. (The +8-8 becomes +4-4 on a 32 bit machine.) Regardless of machine size, the offset is added and subtracted, leaving the original pointer.
Case 2, obj
is not a type**
pointer: The pointer difference expression is ill-formed, so the code doesn't compile.
精彩评论