C++学习笔记(2)---2.5 C++函数编译原理和成员函数的实现

转载自:http://c.biancheng.NET/cpp/biancheng/view/2996.html点击打开链接

从上节的例子可以看出,对象的内存模型中只保留了成员变量,除此之外没有任何其他信息,程序运行时不知道 obj 的类型为 Demo,也不知道它还有一个成员函数 display()。那么,究竟是如何通过对象调用成员函数的呢?

C++函数的编译

C++和C语言的编译方式不同。C语言中的函数在编译时名字不变,或者只是简单的加一个下划线_(不同的编译器有不同的实现),例如,func() 编译后为 func() 或 _func()。

而C++中的函数在编译时会根据命名空间、类、参数签名等信息进行重新命名,形成新的函数名。这个重命名的过程是通过一个特殊的算法来实现的,称为名字编码(Name Mangling)。

Name Mangling 是一种可逆的算法,既可以通过现有函数名计算出新函数名,也可以通过新函数名逆向推演出原有函数名。

Name Mangling 可以确保新函数名的唯一性,只要命名空间、所属的类、参数签名等有一个不同,那么产生的新函数名也不同。

如果你希望看到经算法产生的新函数名,可以只声明而不定义函数,这样调用函数时就会产生链接错误,从报错信息中就可以看到。请看下面的代码:

[cpp] view plain copy

  1. #include<iostream>
  2. using namespace std;
  3. void display();
  4. void display(int);
  5. namespace ns{
  6. void display();
  7. }
  8. class Demo{
  9. public:
  10. void display();
  11. };
  12. int main(){
  13. display();
  14. display(1);
  15. ns::display();
  16. Demo obj;
  17. obj.display();
  18. return 0;
  19. }

该例中声明了四个同名函数,包括两个具有重载关系的全局函数,一个位于命名空间 ns 下的函数,以及一个属于类 Demo 的函数。它们都是只声明而未定义的函数。

编译源代码即可看到错误信息:

小括号中就是 Name Mangling 产生的新函数名,它们都以”?“开始,以区别C语言中的”_“。

上图是VS2010产生的错误信息,不同的编译器有不同的 Name Mangling 算法,产生的函数名也不一样。

__thiscall、cdecl 是函数调用方式,有兴趣的读者可以猛击《函数的几种调用方式》一文深入了解。

除了函数,某些变量也会经 Name Mangling 算法产生新名字,不再赘述。

成员函数的调用

从上图可以看出,成员函数最终被编译成与对象无关的普通函数,如果函数体中没有成员变量,那问题就很简单,不用对函数做任何处理,直接调用即可。

如果成员函数中使用到了成员变量,该怎么办呢?成员变量的作用域不是全局,不经任何处理就无法在函数内部访问。

C++规定,编译成员函数时要额外添加一个参数,把当前对象的指针传递进去,通过指针来访问成员变量。

假设 Demo 类有两个 int 型的成员变量,分别是 a 和 b,并且在成员函数 display() 中使用到了,如下所示:

[cpp] view plain copy

  1. void Demo::display(){
  2. cout<<a<<endl;
  3. cout<<b<<endl;
  4. }

那么编译后的形式类似于:

[cpp] view plain copy

  1. void new_function_name(const Demo *p){
  2. //通过指针p来访问a、b
  3. cout<<p->a<<endl;
  4. cout<<p->b<<endl;
  5. }

调用时的形式类似于:

[cpp] view plain copy

  1. new_function_name(&obj);

这样就完成了对象和成员函数的关联,只不过与我们从表明上看到的相反,不是通过对象找函数,而是通过函数找对象。

这一切都是隐式完成的,对程序员来说完全透明,就好像这个额外的参数不存在一样。

时间: 2024-07-30 20:30:36

C++学习笔记(2)---2.5 C++函数编译原理和成员函数的实现的相关文章

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载、覆盖与隐藏、类型转换运算符、*运算符重载、-&gt;运算符重载

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载.覆盖与隐藏.类型转换运算符.*运算符重载.->运算符重载 一.成员函数的重载.覆盖与隐藏 对于类层次的同名成员函数来说,有三种关系:重载.覆盖和隐藏,理清3种关系,有助于写出高质量的代码. 1.成员函数的重载 重载的概念相对简单,只有在同一类定义中的同名成员函数才存在重载关系,主要特点时函数的参数类型和数目有所不同:但不能出现函数参数的个数和类型均相同,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不立即产生代码.只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调用函数时[不是模板],编译器只需看到函数的声明.类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的.因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中. 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或类模板的成员函

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps

python学习笔记-(七)python基础--集合、文件操作&amp;函数

本节内容 1.集合操作 2.文件操作 3.字符编码与转码 4.函数操作 1.集合操作 集合是一个无序的.不重复的数据组合: 1.1 常用操作 它的作用是: 1)自动去重:列表变成集合,自动去重: 1 2 3 4 >>> list_1 = [1,4,4,5,6,7,9,10] >>> list_1 =set(list_1) >>> print(list_1) {1, 4, 5, 6, 7, 9, 10} 2)关系测试:测试两组数据之间的关系,交集.并集.

【学习笔记】【C语言】static和extern对函数的作用

如果一个程序中有多个源文件(.c),编译成功会生成对应的多个目标文件(.obj),这些目标文件还不能单独运行,因为这些目标文件之间可能会有关联,比如a.obj可能会调用c.obj中定义的一个函数.将这些相关联的目标文件链接在一起后才能生成可执行文件. 外部函数:如果在当前文件中定义的函数允许其他文件访问.调用,就称为外部函数.C语言规定,不允许有同名的外部函数.内部函数:如果在当前文件中定义的函数不允许其他文件访问.调用,只能在内部使用,就称为内部函数.C语言规定不同的源文件可以有同名的内部函数

C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. 1 //单继承的定义 2 class B:public A 3 { 4 < 派生类新定义成员> 5 }; 6 //多继承的定义 7 class C:public A,private B 8 { 9 < 派生类新定义成员> 10 }; 我们这篇主要说单继承. 派生类共有三种C++类继承方式: 公有继承(public) 基类的公有成员和保护成

Python 3 学习笔记(五)----变量、递归和高阶函数

一.变量 1.在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量.2.全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序.3.当全局变量与局部变量同名时:在定义局部变量的子程序内,局部变量起作用:在其它地方全局变量起作用. 1 country = "China" #全局变量 2 3 def change_name(name): 4 global country #如果要在函数中更改全局变量,只有一种方法在函数中声明global+变量名.永远不要用这种方法

static成员函数不能调用non-static成员函数

1 一般类静态成员函数不能调用非静态成员函数 2 static成员函数可以调用构造函数吗? 答案是肯定的,由于static成员函数没有this指针,所以一般static成员函数是不能访问non-static成员的,包括成员函数和成员变量. 由于构造函数特殊性,它从无到有构造一个对象,因此调用它不需要一个instance,也就是不需要this指针来调用,所以在static 函数中能调用构造函数.o 构造函数分成两个阶段:    1. 分配内存:这是从无到有阶段,该阶段结束,产生一个instance

Python 学习之路1 了解Python的编译原理,运行速度

为什么学习Python呢? 目前有许多的开发语言,其中运行速度最快的无疑是C语言了,因为C是最接近机器的语言,但是为什么还有其他的各种语言呢,因为完成C语言的功能可能需要更多的代码,而其他的语言可能需要较少的代码既可以完成C需要很多代码完成的任务,但是呢,Python的运行速度是最慢的.我为啥要学习Python呢,由于下班以后没有什么特别的事情及娱乐活动,刚好Python真的是很方便,并且对大数据及自动化比较有优势,那么刚好打发我无聊的时间,那么学起来吧. 编译原理: C#  编写代码--C#编