您的位置:首页 > 编程语言 > C语言/C++

[变态的C++]“迷路”的函数调用错误

2016-03-12 13:23 225 查看
(转自自己在其他地方写的,原文已废弃)

为了世界和平,人类进步,社会和谐,请面试官和C/C++老师绕道,灰常感谢。

相信你也同意C++是一门很变态的语言,不同意的话,不妨尝试下这个问题(据说是个面试题):

在C++中,我们调用了某个函数,但实际执行情况是“调用错了”,就是说没有执行你所期望的那个函数,调用发生了错误,考虑这是怎么做到的?

具体地来说,就是我们定义两个函数,我们调用了一个函数,执行的却是另一个函数。类似于在下面的代码中加些东西,也可以把函数放进类中,对函数改名加些参数或者修改修饰符,或者再写一些辅助函数都可以,总之让它输出的不太对。

void a(){cout << "func a" << endl;}
void b(){cout << "func b" << endl;}
// === 加些代码 ===
b(); // 输出func a


我们假设编译器是没有bug的,它知道它在干什么,它试图连接的函数和您的期望是一样的。另外,不要直接读写内存区域。

好吧,首先希望您能静下心来想一想这个问题,我建议你暂时先收藏下,然后想清楚了再继续看。如果您实在感到奇怪,您可以先无视我前面提到的一些条件。另外作为善意提醒,我建议您还是要尝试编写代码,我在撰写本文时,一些想到的方案最后费了很长的时间才最终实现。

相信您读到这里时已经有了不少想法了。本文将给出10个方案,不过只有一个完全满足前面的条件。

现在我可以开始我的尝试,一个入手的想法就是利用scope,下面这个例子从某种程度上来说达到了效果(所有的例子均在VC2010,x86下通过,所有的程序都需要加上标准输入输出的头和std命名空间的使用)

//方案甲
void a(){cout << "func a" << endl;}
void b(){cout << "func b" << endl;}

int main()
{
void (*b)()=&a; // 作用域
b(); // 输出func a
return 0;
}


这个例子没有给出类,不过将这两个函数作为静态函数和执行代码放入类中是一样的。这个方案利用了函数指针和scope的误解,这个误解属于人类而不是编译器,所以并不是符合要求的方案,类似的方案还有

//方案乙
void print(){cout << "func a" << endl;}

class C
{
public:
void print(){cout << "func c" << endl;} // 作用域
void func(){::print();}
};

int main()
{
C c;
c.func(); // 输出func a
return 0;
}


这个错误也是作用域的问题,同样属于人的误解。将一些常见的易被忽略的语法错误引入到这个问题中是不错的选择,下面的这个方案也是出于这样考虑:

//方案丙
class A{
public:
virtual void func(){cout << "A::func" << endl;}
virtual void b(){}
};
class B:public A
{
public: virtual void func(){cout << "B::func" << endl;}
};
class C:public B
{
public: virtual void func(){cout << "C::func" << endl;}
};

int main()
{
A& a=false?B():C(); // 注意?运算符的返回类型
a.a(); // 输出B::func
}


您可能认为上面的方案丙不满足两个函数的要求,不过事实上,void b()也同样是class B的成员,当然在这个方案中是凑数的。

除了常见的错误,庞大复杂的C/C++语言的语法一直是我们滋养各种bug的宝库!我们总能在C/C++中发现一些阴暗的的角落,现在我们也能从这个宝库中挑出一些灰尘来试图解决这个问题。

//方案丁
class A;
class B
{
public:
void a(){cout << "func a" << endl;};
void b(){cout << "func b" << endl;};
A* operator->();
};

class A
{
public:
void b(){B b;b.a();}
};
A* B::operator->(){return new A;} // 重载运算符

int main()
{
B b;
b->b(); // 输出func a
}


这个方案看起来有些长,主要是由于两个类之间需要互相访问。我想这是一个比较显然的做法,利用运算符重载直接“迷惑自己”确实是个不错的主意。

//方案戊
class A
{
public:
static void show(...){cout << "func ... " << endl;} // 注意...
static void show(A* z){cout << "func A*" << endl;}
};

int main()
{
A a[10];
A::show(&a); // 输出func ...
return 0;
}


呵呵,看到“…”了吗?

//方案己
class B
{
public:
static int count;
B* z;
B(){z=new B[++count*++count];} // 递归内存溢出
static void a(){cout << "func a" << endl;}
virtual void b(){cout << "func b" << endl;}
};
int B::count=0;
int main()
{
set_terminate(&B::a);
B b;
b.b(); // 输出func a
return 0;
}


我承认在撰写这段代码的时候去翻了些手册。

//方案庚
template<char n> // 注意char的取值范围
class C: public C<n+1>
{
public: void func(){C<n+1>::func();}
};
template<>
class C<0>
{
public: void func(){cout << "func a" << endl;}
};
template<>
class C<300>
{
public: void func(){cout << "func b" << endl;}
};

int main()
{
C<250> c;
c.func(); // 输出func a
return 0;
}


非类型参数模板?元编程?您想多了,其实这里只是类型用得不太妥当。

之所以在这个位置放个割,是因为下面解答的性质和上面不一样。上面的解答都是基于人自己疏忽误解的情况而造成的,下面的方案不是。编译在编译连接函数的时候和您想的是一样。换句话说,除非您检查前面的代码,否则仅仅从声明上没人知道它是不是错了。

//方案辛
class A
{
public:
A(){}
virtual void a(){cout << "func a" << endl;}
};

class B: public A
{
public:
B(A a){memcpy(this, &a, sizeof(A));} // 会覆盖虚函数表,所以这样拷贝时必须注意是同类之间
virtual void b(){cout << "func b" << endl;}
};

int main()
{
B& b=(B)A();
b.b(); // 输出func a
return 0;
}


作为对A的继承,B中是有两个成员,但毕竟不是在一个类中定义的,而且还出现了mencpy这样的东西,所以这个方案不算理想。

//方案壬
class A
{
public:
void a(){cout << "func a" << endl;}
virtual void b(){cout << "func b" << endl;}
};

int main()
{
A& a=A();
void (A::*p)()=&A::a;
*((size_t*)&a)=(size_t)&p; // 写虚函数表
a.b(); // 输出func a
}


这个方案只有一个类了,除了都是定义在一个类中外,函数a()和b()没有任何关系,请留意它们的命名是不同的,这使得这两个函数之间不可能存在把覆盖隐藏的情况。不过正如你在代码中说看到的那样,这个解答很暴力,它直接读写了内存。(这个方案是可以构造得更加隐晦一点,比如仅仅采用++–调整内存值,还有联合体,总之不会让别人看到对解引用赋值的情形。)

下面的例子就是我想给出的答案,它同样很简单,简单到我在回过头去撰写前面的方案时觉得下面的方案是那么索然无味。

//示例癸
class A
{
public: virtual void a(){cout << "A::a" << endl;}
};
class B
{
public: virtual void b(){cout << "B::b" << endl;}
};
class AB: public A, public B
{
public:
virtual void a(){cout << "AB::a" << endl;}
virtual void b(){cout << "AB::b" << endl;}
};
int main()
{
AB* ab=(AB*)(void*)(B*)new AB;
ab->a(); // 输出AB::b
}


最后您可能发现,您的方案果断没有出现在本文中,在此我一定向您请教,希望得到您的指点。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息