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

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

===================割===================

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

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

具体地来说,就是我们在一个类上定义两个函数,我们调用了一个函数,执行的却是另一个函数。我们假设编译器是没有bug的,它知道它在干什么,它试图连接的函数和您的期望是一样的。另外,不要直接读写内存区域。

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

===================割===================

相信您读到这里时已经有了不少想法了。本文将给出10个方案,不过只有一个完全满足前面的条件。
现在我可以开始我的尝试,一个入手的想法就是利用scope,下面这个例子从某种程度上来说达到了效果(所以的例子均在VC2010,x86下通过,所有的程序都需要加上标准输入输出的头和标准命名空间的使用)

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

int main()
{
	void (*b)()=&a;
	b();
	return 0;
}

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

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

class C
{
public:
	void print(){cout << "func b" << endl;}
	void func(){::print();}
};

int main()
{
	C c;
	c.func();
	return 0;
}

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

//方案丙 输出B::func
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();
}

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

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

//方案丁 输出func a
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 ...
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);
	return 0;
}

呵呵,看到“…”了吗?

//方案己 输出func a
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();
	return 0;
}

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

//方案庚 输出func a
template<char n>
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();
	return 0;
}

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

===================咦,怎么现在割了===================

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

//方案辛 输出func a
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();
	return 0;
}

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

//方案壬 输出func a
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();
}

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

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

//示例癸 输出AB::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();
}

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

fuzzytalker

国内某综合类学校85后。常年戴啤酒瓶底。除写代码、听音乐和睡觉外无不良嗜好。积极友情置身工坊工作。

More Posts

One Response

Leave a Reply