请解释一下这个进行转换和类型检查的核心宏
以下代码来自必须用 C 和 C++ 编译的现有应用程序。有一个宏:
/* 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))))
其用法如下:
ISomeInterface *object;
CoCreateInstance(&CLSID_SomeInterfaceImpl, NULL,
CLSCTX_INPROC_SERVER, COMPTR(ISomeInterface, &object))));
这里的想法是 CoCreateInstance()
的最后两个参数是 IID&
和 void**
并且该宏获取 ISomeInterface**
并将其转换为 IID&
和 void**
同时强制执行编译时检查传递的地址代替ISomeInterface**
确实是 ISomeInterface*
指针变量的地址。
好吧,但是复杂的表达有什么必要呢
((obj) + (sizeof((obj)-(type **)(obj))) \
- (sizeof((obj)-(type **)(obj)))
?我看到类型检查是通过 (obj)-(type**)(obj)
子表达式强制执行的。需要先加上然后减去 sizeof()
吗?在转换为 void**
之前,需要先转换为 void*
吗?
我想同样可以这样做:
#define COMPTR(type, obj) &IID_##type, \
(void **)(sizeof((obj)-(type**)(obj)), obj)
这里逗号运算符的第一部分将包含一个 sizeof()
,它将强制进行类型检查并计算为常量,第二部分将产生相同的指针并且指针将被转换为void**
。
原来的宏可以做哪些我建议的宏不能做的事情?出现这些并发症有什么必要?
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))))
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
也许原作者不知道逗号运算符?这在 C/C++ 程序员中并非闻所未闻。
Maybe the original author wasn't aware of the comma operator? This is not exactly unheard of among C/C++ programmers.
也许原作者不知道函数模板。该宏请求被函数模板取代。
显然,
CoCreateInstance
的第四个参数是指向某个 IID 类型的全局对象的指针,该对象属于当前的type
(COMPTR 的类型参数)。CoCreateInstance
的第五个也是最后一个参数应该是一个type**
指针。相反,函数
CoCreateInstance
采用一个void**
(是的!)指针作为其最后一个参数,该指针是通过转换假定的type**
指针获得的。该转换通过void*
作为中介,因为任何指针都可以转换为 void* 指针或从 void* 指针转换。如果没有 COMPTR 宏中的保护,我们可以传递一个 double* 指针,甚至一个 long long(不是指针!)作为 CoCreateInstance 的第五个参数。当然,如果原作者使用 C++ 所擅长的类型安全性,那么整个混乱局面就可以避免。相反,他/她决定采用 void* 指针路线并将保护放在宏中。
这个傻瓜在做什么:
sizeof
的参数是指针差异表达式(obj)-(type**)(obj)
。如果obj
是一个type**
指针,则为 0(作为类型 ptrdiff_t)。如果 obj 是其他内容,则该指针差异表达式的格式不正确。因此,有两种情况,obj
是一个type**
指针,或者不是。情况 1,
obj
是一个type**
指针:指针差异表达式有效,因此CoCreateInstance
的最后一个参数扩展为(void**)(void*)(obj+8-8)
,假设是 64 位机器。 (在 32 位机器上,+8-8 变为 +4-4。)无论机器大小如何,都会添加和减去偏移量,留下原始指针。情况 2,
obj
不是type**
指针:指针差异表达式格式错误,因此代码无法编译。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 thetype
(type argument to COMPTR) at hand. The fifth and final argument toCoCreateInstance
is supposed to be atype**
pointer.Instead, the function
CoCreateInstance
takes avoid**
(yech!) pointer as its last argument, obtained by casting the supposedtype**
pointer. The cast goes throughvoid*
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 along long
(not a pointer!) as the fifth argument toCoCreateInstance
. 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)
. Ifobj
is atype**
pointer, this is 0 (as type ptrdiff_t). Ifobj
is something else, this pointer difference expression is ill-formed. So, two cases,obj
is atype**
pointer, or it isn't.Case 1,
obj
is atype**
pointer: The pointer difference expression is valid, so the last argument toCoCreateInstance
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 atype**
pointer: The pointer difference expression is ill-formed, so the code doesn't compile.