编译器是如何被诱骗提供指向封闭 class 的指针的?
How is the compiler tricked into providing a pointer to the enclosing class?
我正在阅读 article 关于 C++ 如何没有字段访问器作为语言的一部分的文章。
在 post 的末尾,作者给出了一个基于宏的解决方案,该解决方案模拟了 classes 的字段访问器:
// a little trick to fool compiler we are not accessing NULL pointer here
#define property_offset(type, name) \
(((char*)&((type*)(0xffff))->name) - (char*)(0xffff))
#define property_parent(type, name) \
((type*)((char*)(this) - property_offset(type, name)))
// macro defining property
#define property(type, name, parent) \
struct name##_property { \
operator type() { return property_parent(parent, name)->get_##name(); } \
void operator=(type v) { property_parent(parent, name)->set_##name(v); } \
\
private: \
char zero[0]; \
} name
// our main class
class Node {
/* visitCount will act as a field accessor */
property(int, visitCount, Node);
};
当我 运行 通过预处理器时,我得到:
class Node {
struct visitCount_property {
operator int() { return ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->get_visitCount(); }
void operator=(int v) { ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->set_visitCount(v); }
private: char zero[0];
} visitCount;
};
我的想法是我还会添加我自己的实现:
int get_visitCount();
void set_visitCount(int v);
并且看起来好像 visitCount
被直接访问了。
但是,这些函数实际上会在幕后调用:
Node n;
n.visitCount = 1; //actually calls set method
cout << n.VisitCount; //actually calls get method
我想了解更多有关访问封闭 class 的技巧:
((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))
0xffff
的相关性是什么?
在十进制中是:65535
。
这如何欺骗编译器访问包含 visitCount class 的 class?
我还看到这在 MSVC 上不起作用,所以我想知道是否有一种标准的方法来完成这个黑客正在做的事情。
0xffff
没有相关性。这只是一些数字。它可能为零(实际上如果是的话会更容易)。让我们将其分解并重写 0xffff
为 addr
:
(((char*)&((type*)(addr))->name) - (char*)(addr))
(type*)(addr)
只是给了我们一些从 addr
开始的 type*
。这是一个reinterpret_cast
。所以我们称之为 obj
:
(((char*)&obj->name) - (char*)(addr))
我们甚至可以将第二个 addr
替换为 obj
- 这是错误的类型,但无论如何我们都在进行转换,它使正在发生的事情更加清晰:
(((char*)&obj->name) - (char*)(obj))
&obj->name
只是给了我们一个指向那个特定成员的指针,所以我们称它为 mem_ptr
((char*)mem_ptr) - (char*)(obj)
现在很清楚了 - 我们正在获取成员的地址(作为 char*
)并减去父对象的地址(作为 char*
)。我们使用 0xffff
只是为了在两个地方有相同的初始地址。
注意C++标准也直接为此定义了一个宏。它被称为offsetof
。警告 "If type is not a standard-layout class (Clause 9), the results are undefined."
我正在阅读 article 关于 C++ 如何没有字段访问器作为语言的一部分的文章。
在 post 的末尾,作者给出了一个基于宏的解决方案,该解决方案模拟了 classes 的字段访问器:
// a little trick to fool compiler we are not accessing NULL pointer here
#define property_offset(type, name) \
(((char*)&((type*)(0xffff))->name) - (char*)(0xffff))
#define property_parent(type, name) \
((type*)((char*)(this) - property_offset(type, name)))
// macro defining property
#define property(type, name, parent) \
struct name##_property { \
operator type() { return property_parent(parent, name)->get_##name(); } \
void operator=(type v) { property_parent(parent, name)->set_##name(v); } \
\
private: \
char zero[0]; \
} name
// our main class
class Node {
/* visitCount will act as a field accessor */
property(int, visitCount, Node);
};
当我 运行 通过预处理器时,我得到:
class Node {
struct visitCount_property {
operator int() { return ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->get_visitCount(); }
void operator=(int v) { ((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))->visitCount) - (char*)(0xffff))))->set_visitCount(v); }
private: char zero[0];
} visitCount;
};
我的想法是我还会添加我自己的实现:
int get_visitCount();
void set_visitCount(int v);
并且看起来好像 visitCount
被直接访问了。
但是,这些函数实际上会在幕后调用:
Node n;
n.visitCount = 1; //actually calls set method
cout << n.VisitCount; //actually calls get method
我想了解更多有关访问封闭 class 的技巧:
((Node*)((char*)(this) - (((char*)&((Node*)(0xffff))
0xffff
的相关性是什么?
在十进制中是:65535
。
这如何欺骗编译器访问包含 visitCount class 的 class?
我还看到这在 MSVC 上不起作用,所以我想知道是否有一种标准的方法来完成这个黑客正在做的事情。
0xffff
没有相关性。这只是一些数字。它可能为零(实际上如果是的话会更容易)。让我们将其分解并重写 0xffff
为 addr
:
(((char*)&((type*)(addr))->name) - (char*)(addr))
(type*)(addr)
只是给了我们一些从 addr
开始的 type*
。这是一个reinterpret_cast
。所以我们称之为 obj
:
(((char*)&obj->name) - (char*)(addr))
我们甚至可以将第二个 addr
替换为 obj
- 这是错误的类型,但无论如何我们都在进行转换,它使正在发生的事情更加清晰:
(((char*)&obj->name) - (char*)(obj))
&obj->name
只是给了我们一个指向那个特定成员的指针,所以我们称它为 mem_ptr
((char*)mem_ptr) - (char*)(obj)
现在很清楚了 - 我们正在获取成员的地址(作为 char*
)并减去父对象的地址(作为 char*
)。我们使用 0xffff
只是为了在两个地方有相同的初始地址。
注意C++标准也直接为此定义了一个宏。它被称为offsetof
。警告 "If type is not a standard-layout class (Clause 9), the results are undefined."