分类 c/c++ 下的文章

c++类函数编译链接问题

1. 缘由

由于前段时间遇到类外定义函数出现链接错误,而类内定义却链接成功,所以花了点时间从汇编角度来分析c++类函数编译链接问题

2. 对函数的理解

可能很多人对类函数和全局函数的理解不够深刻,所以先简单说明一下。从机器语言角度理解,函数本质上就是代码区的地址,所以无论全局还是类函数都仅是一个地址而已。C全局函数用函数名唯一标记这个地址,C++全局函数用函数名和参数类型名来唯一标记,而C++类函数则用类名、函数名、参数类型名来唯一标记,所以C++有面向对象、同名函数可重载等特性。事实上,类静态函数和非静态函数的调用机制本质上都是函数的调用机制,参数进栈->跳转至函数地址->局部变量进栈->函数逻辑->局部变量出栈->参数出栈->返回调用前地址,对于非静态函数,无非把类对象本身作为入栈参数传递给函数,所以类非静态函数至少有一个参数入栈,以下是类非静态函数的反汇编

图一.jpg

3. 函数在类内定义和类外定义区别

很多人甚至很多权威书籍认为类内定义的函数属于内联函数,事实上并非如此,若编译时采用默认优化选项(O=0),则不属于内联函数,只有设置优化选项(O>=1),类内定义的函数才是内联的,而类外定义的函数,除非加上inline关键字,否则始终不是内联函数,以下是类内定义函数的反汇编

图二.jpg

4. 链接器对函数的处理

若a.h中定义一个函数,b.cpp和c.cpp均包含a.h,则链接时会提示函数重复定义。同样若在a.h中定义一个类,并在类外部定义一个函数,则链接同样会提示函数重复定义,但在类内部定义一个函数,则链接不会报错。很多人认为在类内部定义函数被内联了,当然不会报错,但事实不然,若此时采用默认优化选项(O=0),同样也不会报错。学过汇编的都应该知道,代码区是分节的,通常代码区首节是.init用于进程初始化,我们写的代码基本都在.text节,通过对b.o和c.o进行反汇编发现,全局函数和类外定义的函数均在.text节,而类内定义的函数则在.text.fun节,fun为函数名。因此对于全局和类外定义的函数而言,都在.text节,链接器在合并.text内的代码时,当然会报函数重复定义,而对于类内定义的函数而言,在.text.fun节中,链接器首先把b.o中.text.fun节合并到.text,之后在合并c.o中.text.fun节时,发现已经合并过了,则会跳过,当然就不会报函数重复定义。对于模板函数就更特殊了,无论是全局模板函数还是在类内部或外部定义的模板函数,编译器均会给模板函数分配代码节.text.fun,因此模板函数不存在链接时重复定义问题。以下是类内和类外定义函数的反汇编

图三.jpg