我不小心调用了一个没有自己的 class 对象的成员函数。但这是如何工作的呢?
I accidentally called a member function without own class object. But how does this work?
这是我的代码。
class IService {
};
class X_Service {
public:
void service1() {
std::cout<< "Service1 Running..."<<std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
return 0;
}
我不明白这是怎么回事。我没有继承 IService 也没有创建 X_Service 对象,但它可以工作。
有人可以解释一下吗?
在后台,您的编译器会将所有这些函数转换为机器代码,基本上只是跳转到内存中的特定地址,然后执行存储在那里的命令。
成员函数只是一个函数,除了局部变量和参数外,还有一块存储class对象地址的内存。当您使用 this
关键字时,那块内存保存着您正在访问的地址。
如果您在错误的对象或 nullptr 上调用成员函数,那么您基本上只是让 this
指针指向无效的东西。
你的函数没有访问 this
,这就是你的程序没有崩溃的原因。
也就是说,这仍然是未定义的行为,任何事情都有可能发生。
你的困惑可能来自于这样一种误解,即某些东西可以编译和运行而不会崩溃,所以它 "works"。这不是真的。
有很多方法可以打破语言规则并仍然编写可编译和运行的代码。通过在此处使用 reinterpret_cast
并进行无效转换,您违反了语言规则,并且您的程序具有未定义的行为。
这意味着它看起来可以工作,它可能会崩溃,或者它可能会做一些与您的预期完全不同的事情。
在你的情况下它似乎有效,但它仍然是 UB,代码无效。
当您 reinterpret_cast
将函数或成员函数指针指向不同类型时,永远不允许调用结果指针,除非您先将其转换回其原始类型并通过该类型进行调用。
违反此规则会导致 undefined behavior。这意味着您失去了程序将以任何特定方式运行的任何语言保证,缺少特定编译器的额外保证。
reinterpret_cast
通常是危险的,因为它完全绕过了类型系统。如果你使用它,你需要始终通过查看语言规则来验证自己,是否明确定义了转换和结果的使用方式。 reinterpret_cast
告诉编译器您知道自己在做什么并且您不想要任何警告或错误,即使结果是无意义的。
所以,我玩得很开心,对代码进行了一些操作。这也是一个经验性的答案。这种做事方式有很多陷阱可能会导致堆栈损坏,因此我稍微更改了代码以使其不发生堆栈损坏,但有点显示它发生了什么。
#include <iostream>
class IService {
public:
int x;
};
class X_Service {
public:
int x;
void service1() {
this->x = 65;
std::cout << this->x << std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
std::cout << service.x << std::endl;
std::cin.get();
X_Service derp;
(derp.service1)();
std::cout << derp.x << std::endl;
return 0;
}
所以从一开始,auto
就给了你创建一个 none 类型安全指针的能力 void (IService::*)()
而且对象本身的实例是 this->
而不管无论你是从什么 class 隐身继承的成员函数。唯一的问题是实例的第一个变量是根据您隐身继承的 class 的第一个变量来解释的,如果类型不同,这可能会导致堆栈损坏。
获得很酷的输出但不可避免地导致堆栈损坏的方法,您可以做以下有趣的事情。
class IService {
public:
char x;
};
您的 IDE 将检测 IService 对象的堆栈损坏,但得到
的输出
65
A
有点值得,但您会发现在进行这种隐性继承时会出现问题。
我也在使用 86x 编译器。所以基本上我的变量都排好了。比方说,如果我在 Iservice 中的 int x 之上添加一个 int y,这个程序将输出无意义的内容。基本上它只起作用,因为我的 classes 是二进制兼容的。
这是我的代码。
class IService {
};
class X_Service {
public:
void service1() {
std::cout<< "Service1 Running..."<<std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
return 0;
}
我不明白这是怎么回事。我没有继承 IService 也没有创建 X_Service 对象,但它可以工作。 有人可以解释一下吗?
在后台,您的编译器会将所有这些函数转换为机器代码,基本上只是跳转到内存中的特定地址,然后执行存储在那里的命令。
成员函数只是一个函数,除了局部变量和参数外,还有一块存储class对象地址的内存。当您使用 this
关键字时,那块内存保存着您正在访问的地址。
如果您在错误的对象或 nullptr 上调用成员函数,那么您基本上只是让 this
指针指向无效的东西。
你的函数没有访问 this
,这就是你的程序没有崩溃的原因。
也就是说,这仍然是未定义的行为,任何事情都有可能发生。
你的困惑可能来自于这样一种误解,即某些东西可以编译和运行而不会崩溃,所以它 "works"。这不是真的。
有很多方法可以打破语言规则并仍然编写可编译和运行的代码。通过在此处使用 reinterpret_cast
并进行无效转换,您违反了语言规则,并且您的程序具有未定义的行为。
这意味着它看起来可以工作,它可能会崩溃,或者它可能会做一些与您的预期完全不同的事情。
在你的情况下它似乎有效,但它仍然是 UB,代码无效。
当您 reinterpret_cast
将函数或成员函数指针指向不同类型时,永远不允许调用结果指针,除非您先将其转换回其原始类型并通过该类型进行调用。
违反此规则会导致 undefined behavior。这意味着您失去了程序将以任何特定方式运行的任何语言保证,缺少特定编译器的额外保证。
reinterpret_cast
通常是危险的,因为它完全绕过了类型系统。如果你使用它,你需要始终通过查看语言规则来验证自己,是否明确定义了转换和结果的使用方式。 reinterpret_cast
告诉编译器您知道自己在做什么并且您不想要任何警告或错误,即使结果是无意义的。
所以,我玩得很开心,对代码进行了一些操作。这也是一个经验性的答案。这种做事方式有很多陷阱可能会导致堆栈损坏,因此我稍微更改了代码以使其不发生堆栈损坏,但有点显示它发生了什么。
#include <iostream>
class IService {
public:
int x;
};
class X_Service {
public:
int x;
void service1() {
this->x = 65;
std::cout << this->x << std::endl;
}
};
int main() {
IService service;
auto func = reinterpret_cast<void (IService::*)()>(&X_Service::service1);
(service.*(func))();
std::cout << service.x << std::endl;
std::cin.get();
X_Service derp;
(derp.service1)();
std::cout << derp.x << std::endl;
return 0;
}
所以从一开始,auto
就给了你创建一个 none 类型安全指针的能力 void (IService::*)()
而且对象本身的实例是 this->
而不管无论你是从什么 class 隐身继承的成员函数。唯一的问题是实例的第一个变量是根据您隐身继承的 class 的第一个变量来解释的,如果类型不同,这可能会导致堆栈损坏。
获得很酷的输出但不可避免地导致堆栈损坏的方法,您可以做以下有趣的事情。
class IService {
public:
char x;
};
您的 IDE 将检测 IService 对象的堆栈损坏,但得到
的输出65
A
有点值得,但您会发现在进行这种隐性继承时会出现问题。
我也在使用 86x 编译器。所以基本上我的变量都排好了。比方说,如果我在 Iservice 中的 int x 之上添加一个 int y,这个程序将输出无意义的内容。基本上它只起作用,因为我的 classes 是二进制兼容的。