关于this指针的深入探讨
任何一个对象都是指向自身的this指针.我想深入了解一下this指针.
1:this指针是什么时候创建的?
构造对象前,构造对象后,还是其他?
2:this指针存放在何处?
堆,栈,全局变量,还是其他?
3:this指针如何传递给类中函数的?
绑定?还是在函数参数的首参数就是this指针.那么
this指针又是如何找到类实例后函数的?
4:this指针如何访问类中变量的/?
5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的
位置可以直接使用吗?
6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?
7:这些编译器如何做到的?
8:能否模拟实现?
问题点数:100、回复次数:136Top
1 楼oo(为了名副其实,努力学习oo技术ing)回复于 2006-01-24 10:12:19 得分 0
每一个非静态的成员函数都有一个隐含参数,就是这个this
所以你提的问题大部分不是问题。Top
2 楼healer_kx(甘草(楼主揭贴吧,我们这些上班灌水的也不容易))回复于 2006-01-24 10:24:18 得分 1
this指针可以被简单的理解为对象的地址,可以说就是首地址(暂时先不考虑多继承)
调用函数就是
mov ecx,[obj]
call function
这样,this就是ecx的值了.
Top
3 楼healer_kx(甘草(楼主揭贴吧,我们这些上班灌水的也不容易))回复于 2006-01-24 10:24:42 得分 0
多继承的时候考虑一下类型的便宜就可以了.Top
4 楼windking21(想玩玩WOW 真的那么难吗)回复于 2006-01-24 10:29:45 得分 5
【转】
this 可以直接访问一个隐藏的指针,这个指针就称为 this 指针。它实际上是类定义中一个缺省的预定义的指针。当调用一个成员函数时,系统保证this 指针始终指向产生这个调用的对象,并将该指针作为一个变元自动传递给该函数。同时,只有对象和成员函数之间才存在 this 指针。
【示例】下面一段程序创建一个叫做power的类来计算一个数的幂:
power
{
private:
double b, val;
int e;
public:
power(double base, int exp);
double get_power() { return val;}
};
power::power(double base int exp)
{
b = base;
e = exp;
val = 1;
if(exp == 0)
return;
for(; exp > 0; exp--)
val = val * b;}
void main()
{
power x(4.0, 2), y(2.5, 1), z(5.7, 0);
cout << x.get_power() <<"";
cout << y.get_power() <<"";
cout << z.get_power() <<"\n";
}
在一个类的成员函数内部(如power()),可以直接引用一个类的数据成员,而无需用任何对象或类的限制性说明。所以在 power 类的构造函数 power() 里语句b = base; 意味着参数 base 的值将被赋予产生这个调用的对象中的数据成员 b。在程序中分别定义了 power 的三个对象 x、y 和z,编译器将对象的成员函数与同一对象的数据成员在调用时联系在一起,为此编译器实际上给成员函数传递了一个隐藏的指向函数调用所要引用的对象的指针,即 this 指针。相同的语句可以改写如下:
this->b = base;
这表明,当发生函数调用时(例如当新建一个 power 类的对象x时),编译器传给构造函数 power() 一个指向对象 x 的 this 指针,并隐式地使用这个指针访问属于对象x的b的拷贝。其实上述 power() 函数中的 b=base 等语句只是简写形式,下面是用 this 指针改写的完整的 power()函数:
power::power(double base , int exp)
{
this->b = base;
this->e = exp;
this->val = 1;
if(exp == 0) return;
for(; exp>0; exp--)
this->val = this->val*this->b;
}
在这个例子中,数据成员的名字前缀表达式 this-> 是合法的但没有什么效果,因为this 指针的使用本来就是隐式的。友元函数不是类的成员,而且没有this指针,静态成员函数也没有 this 指针。但在另一些情况下,必须用 this 指针明确当前调用的对象,如在重载运算符时,可以看到 this 指针是非常重要的,它在某些类型的链表管理中往往也是必要的
Top
5 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 10:41:53 得分 0
系统保证this 指针始终指向产生这个调用的对象,并将该指针作为一个变元自动传递给该函数?
我想问的是系统如何保证的?或者说如何实现这种保证的?
Top
6 楼iamcaicainiao(老菜,长征)回复于 2006-01-24 10:42:56 得分 1
学习学习。
新官上任一把火阿Top
7 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 10:45:47 得分 0
有一点可以肯定的是,this指针是在创建对象前创建.
this指针放在栈上,在编译时刻已经确定.
并且当一个对象创建后,并且运行整个程序运行期间只有一个this指针.
Top
8 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 10:48:39 得分 0
healer_kx
this也是指针,是指针肯定有地址了!
this指向的肯定是类中对象数据地址,,或者你认为是首地址..
但是this自身那?他是一个指针,,肯定也有自身的地址.
我想办法的得到这个this本身的地址,是不是就可以操纵这对象了!Top
9 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 10:53:30 得分 0
windking21
我相信来这里的谁都知道this指针,可能上面的问题没有表达清楚
我想问:
如果让你来写编译器,你怎么实现this指针?
Top
10 楼iamcaicainiao(老菜,长征)回复于 2006-01-24 10:55:56 得分 0
我觉得 ugg(逸学堂(exuetang.net)) 你能够得到 this本身的地址,然后你还应该知道这个地址里面的存储方式(存储对象的地址,或者首地址)吧。也许这样就可以操纵对象了。Top
11 楼iamcaicainiao(老菜,长征)回复于 2006-01-24 11:01:52 得分 0
应该是一个隐藏的参数吧。永远和对象实例同生共死的一个玩意吧Top
12 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:06:11 得分 0
可以的到this的地址,但是无法解析里面的内容.
下面是我的测试
>eval this
0x0012fec4 {T=5 T1=6 }
T: 5
T1: 6
>eval &this
0x0012fd58 "宁"
>eval *&this
-60// AA aa;以上部分
>eval this
0x00371438 {T=7 T1=8 }
T: 7
T1: 8
>eval &this
0x0012fd58 "87"
>eval *&this
56 '8'// AA *p = new AA();
Top
13 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:06:25 得分 0
可以的到this的地址,但是无法解析里面的内容.
下面是我的测试
>eval this
0x0012fec4 {T=5 T1=6 }
T: 5
T1: 6
>eval &this
0x0012fd58 "宁"
>eval *&this
-60// AA aa;以上部分
>eval this
0x00371438 {T=7 T1=8 }
T: 7
T1: 8
>eval &this
0x0012fd58 "87"
>eval *&this
56 '8'// AA *p = new AA();
Top
14 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:08:04 得分 0
还是那句话,如果让你写编译器,你将如何实现this?Top
15 楼iamcaicainiao(老菜,长征)回复于 2006-01-24 11:08:16 得分 0
怀疑它有它独特的存储方式和解析方法的。Top
16 楼wingfiring(非典型秃子)回复于 2006-01-24 11:29:22 得分 0
在调用成员函数的时候产生的。
例如:foo.fun();编译器在调用fun的时候,会把foo的地址,作为fun的隐含第一个参数传递进去。
当然,fun是thiscall调用约定,实际上通常是通过cx寄存器传递的.Top
17 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:40:11 得分 0
ECX传递this指针到CUP,追踪寄存器确实如此!
EAX = CCCCCCCC EBX = 7FFDF000 ECX = 00371438
EDX = 00000000 ESI = 00000000 EDI = 0012FD60
EIP = 0041C693 ESP = 0012FC88 EBP = 0012FD60
EFL = 00000212
至于说调用函数时产生,this指针不敢苟同!
个人认为编译器在对象创建前先创建的this指针!
Top
18 楼oosky2004(我要好东西)回复于 2006-01-24 11:48:21 得分 0
在我的眼中,类对象,就是一个结构体。
这个结构体中包含了类成员和非静态成员函数的指针(此为间接函数指针)。
this指针就是这个结构体的地址。
哈哈,不知道这种说法对不对!Top
19 楼oosky2004(我要好东西)回复于 2006-01-24 11:49:43 得分 0
用C语言应该能模拟简单的类。
Top
20 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:57:14 得分 0
类对象,就是一个结构体。
这个结构体中包含了类成员,就是这些..如果时虚类(包括虚函数的类)
在加一个vptr指针.这就是一个类对象.
如果有后边的那些情况就不用this指针了!Top
21 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 11:58:35 得分 0
thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成
员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,th
iscall意味着参数从右向左入栈
如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针
在所有参数压栈后被压入堆栈。
对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
Top
22 楼ddddh(叶君临)回复于 2006-01-24 13:15:28 得分 10
@ ugg(逸学堂(exuetang.net))
"有一点可以肯定的是,this指针是在创建对象前创建.
this指针放在栈上,在编译时刻已经确定.
并且当一个对象创建后,并且运行整个程序运行期间只有一个this指针."
"至于说调用函数时产生,this指针不敢苟同!
个人认为编译器在对象创建前先创建的this指针!"
以上我觉得都是错误的言论。
this可以理解成一个局部变量,也就是说,只在类的成员函数内部出现。“整个程序运行期间只有一个this指针”是从何说起?
至于“编译器在对象创建前先创建this指针”,人们不禁要问:
1. this指针指向对象,对象都还没有被创建,你指向哪里?
2. 就算你能"创建",你把它存在哪里?
this指针正如前面某位仁兄所言,就是一个隐藏的参数(对程序员不可见,对编译器可见),让我们来看一段示例:
struct foo
{
void bar()
{
_i = 0;
}
int _i;
};
void foobar()
{
foo f;
f.bar();
}
使用gcc对其汇编得到:
void foobar()
{
pushl %ebp
movl %esp, %ebp
subl $8, %esp
leal -4(%ebp), %eax // 这里,得到foo的地址,即 %eax = &foo
movl %eax, (%esp) // 相当于push %eax
call __ZN3foo3barEv // 调用 foo::bar()
leave
ret
}
void foo::bar()
{
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax // 从栈里取出调用者压入的参数,即所谓的“this”,付给%eax
movl $0, (%eax) // this->_i = 0
popl %ebp
ret
}
我想意思应该很清楚了吧?
当你调用
foo.bar()或者
pf->bar()的时候,
编译器帮你自动把
&foo或者pf压入堆栈。
而压入堆栈的值,在foo::bar()里面作为一个约定,使用this可以引用到,仅此而已。没有什么花头的。
Top
23 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:01:24 得分 0
1:整个程序运行期间只有一个this指针”是从何说起?
this指针只能在程序运行时,看见其地址.楼上可以跟踪进去看看,是不是this指针只有一个特定地址?并且楼上,你也没有在运行刻得到这个this指针.
2:this指针指向对象,对象都还没有被创建,你指向哪里?就算你能"创建",你把它存在哪里?
你可以定义
char *p;
......
p = new char[100];
一开始不给p指定申请空间,后来才指定,为什么就不能在先定义this,后给你this指针赋值.
楼主可以跟踪this指针.我在VC7.0下跟踪指针结果得到结果如下
>eval this
0x0012fec4 {T=5 T1=6 }
T: 5
T1: 6
>eval &this
0x0012fd58 "宁"
this指针存放在栈上,我们根据栈的特性,先进后出,并且地址有底到高增长.
对比以下这两个地址,就知道那个数据先进栈,先进栈的数据当然先创建.
0x0012fd58
0x0012fec4
3:我想意思应该很清楚了吧?
楼上的楼上已经说的很清脆了.类中的函数调用this指针是通过寄存器ECX指定this的地址.
即类中每个函数都是thiscall类型函数.
根本就不会把this指针入栈操作.this指针入栈只有一种情况就是类的函数是个参数可变函数.
这时候this才会入栈,,其他情况都是通过寄存器ECX传递this地址.
看到楼上写的代码只能说是楼上可以熟练使用this,我想知道的是如果你写编译器,你会怎么实现
this,既然楼主说'"对程序员不可见,对编译器可见'"我现在就是想知道编译器是怎么可见的,不然也不敢谈深入this指针.
Top
24 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:02:02 得分 0
1:整个程序运行期间只有一个this指针”是从何说起?
this指针只能在程序运行时,看见其地址.楼上可以跟踪进去看看,是不是this指针只有一个特定地址?并且楼上,你也没有在运行刻得到这个this指针.
2:this指针指向对象,对象都还没有被创建,你指向哪里?就算你能"创建",你把它存在哪里?
你可以定义
char *p;
......
p = new char[100];
一开始不给p指定申请空间,后来才指定,为什么就不能在先定义this,后给你this指针赋值.
楼主可以跟踪this指针.我在VC7.0下跟踪指针结果得到结果如下
>eval this
0x0012fec4 {T=5 T1=6 }
T: 5
T1: 6
>eval &this
0x0012fd58 "宁"
this指针存放在栈上,我们根据栈的特性,先进后出,并且地址有底到高增长.
对比以下这两个地址,就知道那个数据先进栈,先进栈的数据当然先创建.
0x0012fd58
0x0012fec4
3:我想意思应该很清楚了吧?
楼上的楼上已经说的很清脆了.类中的函数调用this指针是通过寄存器ECX指定this的地址.
即类中每个函数都是thiscall类型函数.
根本就不会把this指针入栈操作.this指针入栈只有一种情况就是类的函数是个参数可变函数.
这时候this才会入栈,,其他情况都是通过寄存器ECX传递this地址.
看到楼上写的代码只能说是楼上可以熟练使用this,我想知道的是如果你写编译器,你会怎么实现
this,既然楼主说'"对程序员不可见,对编译器可见'"我现在就是想知道编译器是怎么可见的,不然也不敢谈深入this指针.
Top
25 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 14:17:48 得分 0
markTop
26 楼piaochen_2002(执子之手,与子偕老!)回复于 2006-01-24 14:20:38 得分 5
1:this指针是什么时候创建的?
构造对象前,构造对象后,还是其他?
this指针在你的类的对象被实例化,也就是有系统的一片内存的时候就存在了.
2:this指针存放在何处?
堆,栈,全局变量,还是其他?
this指针存放在cpu的暂存器里面
3:this指针如何传递给类中函数的?
绑定?还是在函数参数的首参数就是this指针.那么
this指针又是如何找到类实例后函数的?
没错,一般来说编译器会自动在函数的第一个参数加上对象的地址.
没有所谓实例后的函数,类的成员函数在编译的时候已经非成员化了
4:this指针如何访问类中变量的/?
5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的
位置可以直接使用吗?
6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?
这几个问题,看下面的代码:
#include<iostream>
#include<stdio.h>
using namespace std;
class A
{
public:
int a1;
int a2;
static int a3;
static void f(void){};
void f1(void )
{
cout<<"oo"<<endl;
}
};
int A::a3=1;
void main()
{
printf("&A::a1 %d\n",&A::a1);
printf("&A::a2 %d\n",&A::a2);
printf("&A::a3 %d\n",&A::a3);
printf("&A::f %d\n",&A::f);
printf("&A::f1 %d\n",&A::f1);
}
对于类的非静态成员变量a1,a2 &A::a1和&A::a2实际上是它们相对this指针的offset地址,
当 你定义一个 A a, a.a1的时候 在访问的时候实际上是*(this +offset),对于类的静态成员
变量,以及函数,相应的获取地址,实际上是它们在编译的时候分配的地址.
Top
27 楼vollin(林尚义)回复于 2006-01-24 14:21:18 得分 1
struct CA
{
public:
void fun()
{
cout<<this<<endl;
cout<<&this<<endl;//error C2102: '&' requires l-value
}
};
void main()
{
CA a;
cout<<&a<<endl;
a.fun();
}
这说明this根本不是指针,只是一个右值,即只是一个地址。
Top
28 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:22:45 得分 0
刚才做了一个试验.
void *p = (void*)0x0012fd34;
char *pchar = new(p) char[10];
memset(pchar,10,'a');
把p指向this指针所在的地址,发现此处地址.外部不可访问.即对数据不
不可以修改.但是访问
0x0a0a0a0a 处最可能的异常: 0xC0000005: 读取位置 0x0a0a0a0a 时发生访问冲突
>eval &this
0x0012fd34 "宁"
>eval &this
0x0012fd34 "87"
>eval p
0x0012fd34
>eval pchar
0x0012fd34 "荥"
>eval &this
0x0012fd34 "87"
>eval p
0x0012fd34
>eval pchar
0x0012fd34 "嫒A"Top
29 楼piaochen_2002(执子之手,与子偕老!)回复于 2006-01-24 14:26:28 得分 0
详细请看<<深度探索C++对象模型>>Top
30 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:32:25 得分 0
to :vollin(林尚义)
cout<<&this<<endl;//error C2102: '&' requires l-value
楼上要从运行时刻看,编译时刻还没有this指针.当然是错误的了.
Top
31 楼oosky2004(我要好东西)回复于 2006-01-24 14:34:11 得分 0
把this指针看成一个数组的首地址吧。
阿门!
Top
32 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:35:46 得分 0
to:piaochen_2002(执子之手,与子偕老!)
2:this指针存放在何处?
堆,栈,全局变量,还是其他?
this指针存放在cpu的暂存器里面
`~`~`~`~`~`~`~`~
类中的函数都是thiscall类型的,this指针是通过寄存器ECX传递给CPU的,怎么能说是
保存在暂存器中的.Top
33 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:37:58 得分 0
既然深入讨论,大家不放说说this在编译器中是如何实现的?Top
34 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 14:38:58 得分 0
oosky2004(oosky)
把this指针看成一个数组的首地址吧。
阿门!
说话不负责任啦?呵呵!害人不浅!
Top
35 楼oosky2004(我要好东西)回复于 2006-01-24 14:41:47 得分 0
既然深入讨论,大家不放说说this在编译器中是如何实现的?
-------------------------------------------------------
这个问题的讨论现简化为 char *this = "Hello World";
这个this指针如何实现的?Top
36 楼oosky2004(我要好东西)回复于 2006-01-24 14:42:45 得分 0
oosky2004(oosky)
把this指针看成一个数组的首地址吧。
阿门!
说话不负责任啦?呵呵!害人不浅!
这个问题是说的有点过头了。呵呵。Top
37 楼ddddh(叶君临)回复于 2006-01-24 14:56:34 得分 5
@ ugg(逸学堂(exuetang.net))
"类中的函数调用this指针是通过寄存器ECX指定this的地址."
这个...你看一下反汇编出来的代码行不?这里面有用到ecx吗?用ecx可能是一种优化,这样可以少访问两次内存(调用者push一次,被调用者访问栈一次),但是标准并没有规定编译器一定要使用ecx来传递this指针。
“我现在就是想知道编译器是怎么可见的,不然也不敢谈深入this指针”
我上面已经说过了,如果你是foo.bar(),那么编译器就会生成一条把&foo压入栈,或者……mov &foo, %ecx,这样的代码,如果是foo *pF; pF->bar();编译器会生成一条压pF入栈或者mov pf, %ecx 这样的代码。这就是在bar()里面看到的this了。还有问题吗?
BTW: 我觉得this指针没有什么好深入的,基本上这就是全部了,和
--- xxx.c ---
struct foo
{
int _i;
void (*bar)();
};
void fun(struct foo *pF)
{
pf->_i = 0;
}
void foobar()
{
struct foo f;
f.bar = fun;
*(f.bar)(&f); // &f就是所谓的 "this"
}
没有太大的差别。
Top
38 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 15:21:55 得分 0
但是标准并没有规定编译器一定要使用ecx来传递this指针。
这个问题你看看C++中的thiscall,还有在运行期间看看的CPU寄存器!
我现在用的VC7.0,你用的GCC,出现这种问题.
这就是不同编译器对this不同解决方案!
如果基于使用角度来说,没有必要讨论.但是this就是楼上说的这些功能吗?
既然楼上说没有太大区别,那还是有区别的!现在我只是想让大家转换一个思维,
平时我们都是使用this,如果让你实现this,确切的说模拟实现this你会怎么做?
Top
39 楼poweryitian(弋天)回复于 2006-01-24 15:33:46 得分 0
学习,markTop
40 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 15:35:31 得分 0
说了这么就,你门讨论出了个什么没有呀?this指针的关键技术是什么呀?晕哦!go on...Top
41 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 15:35:44 得分 0
我在<<深度探索C++对象模型>>通篇没有找this这个字眼!
还是抽时间看看先!Top
42 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 15:42:27 得分 0
0041C4F8 mov ecx,dword ptr [pthis]
0041C4FB call TestThis::fun (41B077h)
0041C470 lea ecx,[aa]
0041C473 call TestThis::fun (41B077h)
汇编的情况大家都看见了!
问题是,当你执行的函数体内时,你会发现this指针的地址都是
同一个?为什么我们从这里看见的是把[aa]或者dword ptr [pthis]放入
ecx,而在CPU的寄存器中ECX的值都是相同的!你不觉的奇怪吗?
Top
43 楼forever_new(Forcheny)回复于 2006-01-24 15:49:21 得分 0
好热烈啊!向你们学习!Top
44 楼oosky2004(我要好东西)回复于 2006-01-24 16:12:58 得分 0
0041C4F8 mov ecx,dword ptr [pthis]
0041C4FB call TestThis::fun (41B077h)
0041C470 lea ecx,[aa]
0041C473 call TestThis::fun (41B077h)
汇编的情况大家都看见了!
问题是,当你执行的函数体内时,你会发现this指针的地址都是
同一个?为什么我们从这里看见的是把[aa]或者dword ptr [pthis]放入
ecx,而在CPU的寄存器中ECX的值都是相同的!你不觉的奇怪吗?
----------------------------------------------------------------
难道你写的程序中,一个对象会用两个地址?
Top
45 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 16:24:36 得分 0
大哥我创建的是两个对象!
TestThis aa;
TestThis *pthis = new TestThis();Top
46 楼oosky2004(我要好东西)回复于 2006-01-24 16:34:30 得分 0
试着把aa地址取出来和pthis的值比较看看。
aa.fun();
pthis->fun();
看看这两个汇编的情况。
Top
47 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 16:45:34 得分 0
让我告诉大家原理!Top
48 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 16:46:27 得分 5
#include<iostream>
using namespace std;
class Point
{
public:
Point()
{
cout<<this<<endl;
cout<<this+1<<endl;
cout<<this-1<<endl;
}
};
int main()
{
Point a;
cout<<&a<<endl;
return 0;
}
以上为测试程序:Top
49 楼xtaddqqug(王中)回复于 2006-01-24 16:51:30 得分 0
这是C++语言的机制,看看编译原理Top
50 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 17:00:33 得分 0
楼上this,指向对象自身.
也就是说
Point pp;
this = &pp;
this+1,this-1;都是对相当于&pp+1,&pp-1.
所以本质还不是对this的操作!Top
51 楼piaochen_2002(执子之手,与子偕老!)回复于 2006-01-24 17:01:03 得分 0
LZ在<<深度探索C++对象模型>>里会找到你要的答案的,要仔细看!Top
52 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 17:07:56 得分 0
正在看......
Top
53 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 17:09:01 得分 0
我看的这么辛苦,楼上不妨指点一下,哪章哪节?Top
54 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 17:15:28 得分 0
首先当你要明白的是以上的point,a,public this.....都是一个标号而已。
当你够造一个对象,比如这里的 a,此时编译器分派一个地址0x0012fed7(你的有可能不同,看你用什么编译器了,应该是相同的!)此时编译器把地址付给 keyword 即this指针和对象a,这样你就可用this访问对象a的成员了。这里很关键的一点就是,我们没在class中声明数据成员,你通过this+1,this-1就可得到高位和地位的地址,偏移量为1;当你在CLASS中定义一个数据成员,那this和a的起始地址就被赋予:0x0012fed4了(这是由编译器决定的,每加一个偏移sizeof(type)),此时你再通过this+1和this-1就会得到0x0012fed8和0x0012fed0,偏移量为4,因为你的在class中加入了一个(int)数据成员,其他类型依次类推。到此你就明白了this指针实际就可看成一个数据成员的集合或就叫一个struct{int i;...},这样我门就可访问其成员了,this->i 就好比是 0x0012fed4+0第一个数据成员i。.........未完。Top
55 楼Muf(沐枫)回复于 2006-01-24 17:26:32 得分 40
初学者对this指针真的是很好奇,因为它很神秘。
其实,只要以平常心来看待,this就很平常了。
1. this只能在成员函数中使用。
全局函数,静态函数都不能使用this。
实际上,成员函数默认第一个参数为T* const register this。
如:
class A{public: int func(int p){}};
其中,func的原型在编译器看来应该是: int func(A* const register this, int p);
2. 由此可见,this在成员函数的开始前构造的,在成员的结束后清除。
这个生命周期同任一个函数的参数是一样的,没有任何区别。
当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去。如:
A a;
a.func(10);
此处,编译器将会编译成: A::func(&a, 10);
嗯,看起来和静态函数没差别,对吗?不过,区别还是有的。编译器通常会对this指针做一些优化的,因此,this指针的传递效率比较高--如vc通常是通过ecx寄存器来传递this参数。
3. 回答
#1:this指针是什么时候创建的?
this在成员函数的开始执行前构造的,在成员的执行结束后清除。
#2:this指针存放在何处? 堆,栈,全局变量,还是其他?
this指针会因编译器不同,而放置的位置不同。可能是栈,也可能是寄存器,甚至全局变量。
#3:this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针.那么this指针又是如何找到类实例后函数的?
this是通过函数参数的首参数来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。
#4:this指针如何访问类中变量的/?
如果不是类,而是结构的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,那就很好理解这个问题了。
在C++中,类和结构是只有一个区别的:类的成员默认是private,而结构是public。
this是类的指针,如果换成结构,那this就是结构的指针了。
#5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的位置可以直接使用吗?
this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的。
#6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?
普通的类函数(不论是成员函数,还是静态函数),都不会创建一个函数表来保存函数指针的。只有虚函数才会被放到函数表中。
但是,既使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。
# 7:这些编译器如何做到的?8:能否模拟实现?
知道原理后,这两个问题就很容易理解了。
其实,模拟实现this的调用,在很多场合下,很多人都做过。
例如,系统回调函数。系统回调函数有很多,如定时,线程啊什么的。
举一个线程的例子:
class A{
int n;
public:
static void run(void* pThis){
A* this_ = (A*)pThis;
this_->process();
}
void process(){}
};
main(){
A a;
_beginthread( A::run, 0, &a );
}
这里就是定义一个静态函数来模拟成员函数。
也有许多C语言写的程序,模拟了类的实现。如freetype库等等。
其实,有用过C语言的人,大多都模拟过。只是当时没有明确的概念罢了。
如:
typedef struct student{
int age;
int no;
int scores;
}Student;
void initStudent(Student* pstudent);
void addScore(Student* pstudent, int score);
...
如果你把 pstudent改成this,那就一样了。
它相当于:
class Student{
public:
int age; int no; int scores;
void initStudent();
void addScore(int score);
}
Top
56 楼Muf(沐枫)回复于 2006-01-24 17:34:01 得分 0
纠错:
1. this 是 T* const this
2. 不能够 &this,因为 this是const. 而const是不能取地址的。Top
57 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 17:37:36 得分 0
如果不能取得&this说明楼上调试功底还是欠缺,一点点而已.
不过this却是const型.Top
58 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 17:53:33 得分 0
class Point
{
public:
Point()
{
cout<<this<<endl;
//cout<<this+1<<endl;
//cout<<this-1<<endl;
j=123;
p=(int*)((Point *)((char*)this + offsetof(Point,j)));//我要通过this加偏移量访问j即j=123;
cout<<&j<<endl;
cout<<"着个值通过this+offset访问的"<<*p<<endl;
cout<<"这个值通过this->j访问的"<<this->j<<endl;
}
int getj(){return j;}
private:
int i;
int j;
int * p;
};
int main()
{
Point a;
cout<<"这个值是通过对象a访问的"<<a.getj()<<endl;
return 0;
}
//更可在主函数中访问j,待叙。Top
59 楼JOKER_UFO(JOKER_UFO)回复于 2006-01-24 19:36:48 得分 5
对于楼主的着个问题:
5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的
位置可以直接使用吗?
我认为不成其为问题。但有着个想法挺不错的!不知道楼主可曾学过汇编!
datas segment
message db 'Still in love with c++','$'
datas ends
....
请问我这里有必要用一个地址去存储message这个标号吗?很显然不。它只是标号,我们用它来标记而已。类推到我给的例子中就好比是this指针,我们仅用它来做标记而以,而这个标记是被编译器内嵌了。在我的程序中已给出了几种不同的访问数据成员的方法。这仅个人观点,也是我对this的认识,有不同意见的朋友可提出个人看法。Top
60 楼Muf(沐枫)回复于 2006-01-24 20:18:59 得分 0
ugg(逸学堂(exuetang.net)) :
瞧你说的些什么啊……Top
61 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 21:42:54 得分 0
当运行到函数内部时.
调出命令窗口->输入eval &this就可以了!Top
62 楼Muf(沐枫)回复于 2006-01-24 21:56:00 得分 0
ugg(逸学堂(exuetang.net))
Debug模式下,为了方便调试的原因,基本不对程序作太多的优化。
但是在Release,一般都对程序做了极大限度的优化。
象前面一位老兄说的,this在vc中,被直接优化成寄存器变量ecx,因此,你想获取&this的值,是不可能的。因为寄存器变量在intel的x86系列cpu中是没有地址的。Top
63 楼ugg(逸学堂(exuetang.net))回复于 2006-01-24 22:55:32 得分 0
上面已经告诉你方法了,编译环境VC7.0.
自己测试一下就知道!Top
64 楼Mackz(在相互)回复于 2006-01-25 08:33:07 得分 0
有this指针吗?有吗?真的有吗?
钻牛角尖,呵呵。Top
65 楼iamcaicainiao(老菜,长征)回复于 2006-01-25 08:55:10 得分 0
顶,从头看到尾巴,继续。Top
66 楼oosky2004(我要好东西)回复于 2006-01-25 09:24:07 得分 0
没时间看了。要回家过年去了啊。
Top
67 楼xiao_p(kkk)回复于 2006-01-25 09:36:32 得分 0
期待~~
你们继续
楼主有很多时候确实太过盛气凌人了些~~Top
68 楼ugg(逸学堂(exuetang.net))回复于 2006-01-25 10:36:15 得分 0
讨论只是对事不对人,在这过程中难免会发生口舌之争.
别人能做到的我做不到的,就是我的个人能力问题.首先要做的是解决我自己个人能力问题.
我相信每个人也都是这样.
如果无意中伤了别人表示歉意!
如果想深入了解一个东西,一个事件机制,必须自己动手深入到里面去查看.
如果你不能深入里面,没有跟踪它的每一步,哪来的发言权.
关于this每本将C++都有提到,但是都没有深入讲解过.我们要讨论this
指针的由来,实现,模拟等.这些必须深入其执行内部我们才可能知道.
编译器如何内部实现this不是靠猜出来的,是要用数据说明的.
比如说Muf说不能得到&this,但是我们在运行时刻可以得到&this的地址.
还有debug和release的区别就是设置编译的编译模式,和编译优化,我们可以在debug下
调整编译器,设置各项优化.也可以达到release的优化状态.
至于说ECX寄存器问题,这些都是从thiscall函数明确调用中说明的.
Top
69 楼two_ears(耳朵)回复于 2006-01-25 11:20:15 得分 0
呵呵,真是没想到这个帖子回了这么长,看到累了都。
后面没仔细看,看完了一些,回帖也都不知道该说些什么了。
举手表决吧。
比较赞同,healer_kx(小李探花有飞刀~~~~)、wingfiring(别逗了)(非典型秃子)、piaochen_2002(执子之手,与子偕老!) 等同志的说法......
(论坛不必过于认真,恕不一一列举)
哦,刚看到Mackz(在相互)的回帖,对于这个主题,实在是比较让人喜欢的风格,呵呵。
倒是有一点感想,论坛是自由的,
不过技术论坛有时候还真难办,
水平是参差不齐地,对话是比较困难地,
实在不行大家一乐,不要过于认真。Top
70 楼alen_ghl(东方求*)回复于 2006-01-25 11:39:16 得分 0
ugg(逸学堂(exuetang.net)) ( ) 信誉:100 2006-1-24 10:45:47 得分: 0
ugg(逸学堂(exuetang.net))
有一点可以肯定的是,this指针是在创建对象前创建.
this指针放在栈上,在编译时刻已经确定.
并且当一个对象创建后,并且运行整个程序运行期间只有一个this指针.
-------------------------------------------------------------------
不同意,this指针应该是在调用构造函数时定义并赋值
const C *this = &obj;
首先this指针既然是个指针,他就有指向的对象,如果在创建对象之前就有,请问对象都没有this指向 who?
Top
71 楼zwcboys(kkkdy)回复于 2006-01-25 16:15:16 得分 0
我怎么一个也看不懂Top
72 楼Muf(沐枫)回复于 2006-01-25 16:49:21 得分 0
楼主居然说可以vc调试环境中得到&this的值,我特意试了一回,果然可以得到this的地址:
+ this 0x0012fed8 {n=0x00000001 } A * const
+ &this,m 0x0012fe7c d8 fe 12 00 char *
但是this的地址到底在哪呢?
为此,我特意反汇编了一下,结果果然在意料之中--Debug程序为了能得到this的地址,特意加了料了。
成员函数void A::f(int v){n=v;}程序反汇编如下:
; void A::f(int v){
00411A10 push ebp
00411A11 mov ebp,esp
00411A13 sub esp,44h
00411A16 push ebx
00411A17 push esi
00411A18 push edi
00411A19 mov dword ptr [ebp-4],ecx
; n=v;
00411A1C mov eax,dword ptr [this]
00411A1F mov ecx,dword ptr [v]
00411A22 mov dword ptr [eax],ecx
; }
00411A24 pop edi
00411A25 pop esi
00411A26 pop ebx
00411A27 mov esp,ebp
00411A29 pop ebp
00411A2A ret 4
看啊,this出现了:
00411A1C mov eax,dword ptr [this]
果然,this是一个变量啊。既然是变量那肯定有地址啦,而且可以很肯定的说,地址为:0x0012fe7c。
且慢!!!
我们先看看各个寄存器的值再说:
EAX = 0012FED8 EBX = 7FFD6000 ECX = 0012FED8 EDX = 00000001 ESI = 00000040
EDI = 7C935B4F EIP = 00411A1C ESP = 0012FE30 EBP = 0012FE80 EFL = 00000216
这里,EBP是这个函数的栈框架指针,局部变量存在EBP的负偏移,函数参数存在于EBP的正偏移。
0x0012fe7c 刚好就是 EBP-4!!!我们再看看ebp-4出现的行:
00411A19 mov dword ptr [ebp-4],ecx
呵呵呵,看到这里,是不是觉得vc很无聊啊。
00411A19 mov dword ptr [ebp-4],ecx
00411A1C mov eax,dword ptr [this]
这两句连起来,只不过相当于mov eax, ecx。除了给debug留下一个this的局部变量外,一点意义都没有。这就是在vc的调试环境能够得到&this的迷底了。
那么,在release中,这个函数到底是怎么实现的呢?为了不让这个函数被优化没了,我特意加了 __declspec(noline),结果如下:
;__declspec(noinline) void A::f(int v){
; n=v;
00401000 mov eax,dword ptr [esp+4]
00401004 mov dword ptr [ecx],eax
; }
00401006 ret 4
好简洁啊,只剩下3句了。
第一句 就是将参数v送到eax中
第二句 就是将eax送到this->n中。此处,this就是ecx啦。n因为偏移为0,所以并没有体现出来。
第三句就直接返回了。
BTW,VC7.1在Release中也能够进行断点调试,乘着这个机会,我就再试一下,能不能得到&this的值,结果:
+ this 0x7c92eb94 {k=0x24a48dc3 n=0x00000000 } A * const
&this,m CXX0018: 错误: 错误的寄存器名
&this得不到了。
=======================================
在教训别人之前,要先反省一下自已。Top
73 楼Muf(沐枫)回复于 2006-01-25 16:58:11 得分 0
附测试程序:
#include "stdio.h"
class A
{
public:
int k;
int n;
void f(int v);
};
void main()
{
A a;
a.f(3);
printf("%d", a.n);
}
__declspec(noinline) void A::f(int v){
n=v;
}
Top
74 楼Muf(沐枫)回复于 2006-01-25 16:59:06 得分 0
附测试程序:
#include "stdio.h"
class A
{
public:
//int k;
int n;
void f(int v);
};
void main()
{
A a;
a.f(3);
printf("%d", a.n);
}
__declspec(noinline) void A::f(int v){
n=v;
}
Top
75 楼ugg(逸学堂(exuetang.net))回复于 2006-01-25 21:58:51 得分 0
如果不能取得&this说明楼上调试功底还是欠缺,一点点而已.
不过this却是const型.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果楼上认为这是教训人的话?
哪我还认为下面的话楼上太盛气了.
如果不是类,而是结构的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,那就很好理解这个问题了.
试问在调试状态不能得到一个变量的地址或者变量的值,不是调试功底欠缺吗?
楼上的程序至少证明
纠错:
1. this 是 T* const this
2. 不能够 &this,因为 this是const. 而const是不能取地址的。
两个纠错不是完全正确的,只所以不能得到this的地址说名this是register型,就如楼上一开始说的.
~~~~~~~~~~~~~~~~~~~~~
既然讨论,难免有口舌之争.如果说楼上说上面我说的哪句话
太盛气凌人.我无话可言,确实有点过,并且我已经为说这句话道歉了.但是楼上说成是教训.
哪请问楼上说这么多不就是为了说
在教训别人之前,要先反省一下自已。
出一口恶气吗,来教训我吗?
楼上真的痛下决心,把this指针问题搞清楚.也不就是为了出这口气吗?
至少在这个验证过程中,楼上也明白了不少模糊感念吧!至少看这个帖子的
人因为你的验证可以更深入了解this.数据是最好的说明!
BTW:说这么多,其实并不是为了自己争辩什么.每个一个程序员都是一只
高傲的猫,因为自己可以驾驭程序,以为就可以驾驭一起,所以说话也就越来
越没有边.
技术论坛讨论的是技术,我们能够共同学习,怄气真的不值的.
正所谓不打不相识.很欣赏楼上,如果不嫌弃交个朋友吧!
Top
76 楼Muf(沐枫)回复于 2006-01-25 22:13:33 得分 0
const类型有一个很特殊的地方,就是它允许编译器进行任何可能优化,它既可以被编译成常量,寄存器变量,或其它的什么,因此,const类型是不能取地址的。
-----------------
向楼主郑重道歉,我正在反省之中……Top
77 楼ugg(逸学堂(exuetang.net))回复于 2006-01-25 22:29:04 得分 0
看来Muf没有看短消息的习惯.
MSN:ugg_xchj@hotmail.com
QQ:35091551
其实,这里面有我个人做的不好的地方.Top
78 楼Muf(沐枫)回复于 2006-01-25 23:09:24 得分 0
短消息总是一些csdn的广告,所以就都没看了。Top
79 楼sailor_Song(函数)回复于 2006-01-26 12:28:17 得分 0
我觉得看看深入浅出mfc有好处Top
80 楼losedxyz(我真的一无所有)回复于 2006-01-26 13:53:41 得分 0
markTop
81 楼iamcaicainiao(老菜,长征)回复于 2006-01-26 14:44:38 得分 5
赫赫,不打不相识阿。
正所谓英雄惜英雄。
顶。好铁。真的是好铁。
希望ugg以后,多出点类似的好铁。Top
82 楼Kenny_Glacier(冰坼)回复于 2006-01-26 16:35:53 得分 0
markTop
83 楼dearlee_01(街头老狗)回复于 2006-01-26 17:00:48 得分 1
1:this指针是什么时候创建的?——在你创建对象的时候
构造对象前,构造对象后,还是其他?——构造后
2:this指针存放在何处?——常量区(全局变量)
堆,栈,全局变量,还是其他?
3:this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针.那么
this指针又是如何找到类实例后函数的?——this为类的隐含成员
4:this指针如何访问类中变量的/?——this为类的隐含成员
5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的
位置可以直接使用吗?——不明白什么意思,只要对象存在就能用
6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?——根据具体语言和编译器定
7:这些编译器如何做到的?——看看编译原理
8:能否模拟实现?——一切皆有可能
Top
84 楼sjjf(水晶剑锋)回复于 2006-01-27 11:36:20 得分 0
先mark再说Top
85 楼Kenmark(fenix)回复于 2006-01-27 12:09:10 得分 0
精华啊Top
86 楼yzx0023(无聊客)回复于 2006-01-27 15:31:59 得分 0
......Top
87 楼rigel2001(大宝)回复于 2006-01-27 15:52:49 得分 0
lz问的这些问题我也没搞清楚,很好的问题Top
88 楼Kid4you(Kid4you)回复于 2006-01-27 16:18:03 得分 0
学到了.Top
89 楼force2004(牛牛)回复于 2006-01-27 21:22:48 得分 0
markTop
90 楼sjjf(水晶剑锋)回复于 2006-01-28 12:11:53 得分 3
偶前些时间也在看vc的反汇编代码。
=================================================================
回复人: Muf(沐枫) ( ) 信誉:98 2006-1-25 22:13:33 得分: 0
const类型有一个很特殊的地方,就是它允许编译器进行任何可能优化,它既可以被编译成常量,寄存器变量,或其它的什么,因此,const类型是不能取地址的。
-----------------
向楼主郑重道歉,我正在反省之中……
===================================================
不明白 const不能取地址 是指什么?
46: const int ci = 0;
0040110B mov dword ptr [ebp-10h],0
47: int const *pci = NULL;
00401112 mov dword ptr [ebp-14h],0
48: pci =&ci;
00401119 lea edx,[ebp-10h]
0040111C mov dword ptr [ebp-14h],edx
而且,关于this指针,如果一个类或者结构体没有方法的话,和顺序定义无异。
类名,和结构体名字只是为了给编译器计算偏移的时候提供便利而已。它们描述了内存块的大小和类型分布信息。
this指针的值,可以认为是所定义的内存块的地址。
如果没有virtual方法的话。 类的内存布局也只是有从继承树根节点到这个类的所有节点的类定义的变量而已。同样可以视作一块内存块。
this指针的值也同样可以认为是所定义的内存块的地址。
如果有virual方法的话,那么编译器会在构造函数调用时(基类构造函数调用后,本构造函数实体执行前)给这个类事例的内存块加入一个变量,他的值指向`vftable,代码如下:
mov dword ptr [eax],offset CTest::`vftable' (00422028)
类的方法实际上跟类实例没有太多的关系。可以认为它们是被剥离出来的。
类的方法跟类的实例之间的关联是通过。this指针,这点在很多书上都有说明。
在我所看到的汇编代码中,编译器会为方法调用产生一些代码,这些代码把该类的实例的内存块的地址压入 ecx 地址,
类的方法如果引用到类的某些成员变量的时候,在逻辑上通过this->xx访问,这个通过取ecx
的值加上xx在类中的偏移地址就可以计算出来了,有时候在类方法中,并不直接取ecx寄存器的值来计算。而是把ecx值存入到一个栈空间中再对这个栈空间进行操作。可以认为这个栈空间作为临时变量存放this指针的值。
我不太明白楼主为什么一定要强调
this指针存放在哪儿。在汇编级别里面,一个值只会以三种形式出现,立即数,寄存器值和内存变量值。
不是存放在寄存器就是存放在内存中里面,它们并不是和高级语言变量对应的。
以下是俺对你的问题的解答过程,个人的见解,仅供参考。
1:this指针是什么时候创建的?
构造对象前,构造对象后,还是其他?
2:this指针存放在何处?
堆,栈,全局变量,还是其他?
3:this指针如何传递给类中函数的?
绑定?还是在函数参数的首参数就是this指针.那么
this指针又是如何找到类实例后函数的?
---如果构造对象判断标准是构造函数的话那么是在构造之前。
但是如果class或者struct里面没有方法的话,它们是没有构造函数的。
只能当作c的struct使用。
采用 TYPE xx的方式定义的话,在栈里分配内存,这时候this指针的值就是这块内存的地址。
采用 new的方式创建对象的话,在堆里分配内存,new操作符通过eax返回分配的地址,然后设置给指针变量。
之后去调用构造函数(如果有构造函数的话),时将这个内存块的地址传给ecx,
之后构造函数里面怎么处理看上面的回答。
4:this指针如何访问类中变量的/?
-- 上面也有说明,通过this的值和变量的偏移量来计算某实例的某变量地址。
5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的
位置可以直接使用吗?
-- 在汇编里,没有什么不可以的。
在cpp中你可以取到this指针值后,你可以改变这个类实例的某变量的值,
但是你好象调用不了函数,编译器过不了。
后面几个问题,我回答不了。
:(
Top
91 楼sjjf(水晶剑锋)回复于 2006-01-28 12:16:07 得分 0
过年了,祝大伙儿春节快乐。
Top
92 楼rigel2001(大宝)回复于 2006-01-28 13:54:55 得分 3
我看了几篇文章,this其实是指针,又不是指针,指针一般是指地址变量,可是this并不具有地址变量的一些属性,比如你
1.无法取得this的地址;
2.无法改变this的值;
所以我觉得问题的关键是,this不是一个一般意义上的变量,这样很多问题就烟消云散了.
我把在网上找到的两段代码贴上来,前一段是一个程序,后一段模拟了该程序的真实面貌,大家对照着仔细看一定会理解this的内涵:
#include <string>
#include <iostream>
using namespace std;
struct X {
private:
int len;
char *ptr;
public:
int GetLen() {
return len;
}
char * GetPtr() {
return ptr;
}
X& Set(char *);
X& Cat(char *);
X& Copy(X&);
void Print();
};
X& X::Set(char *pc) {
len = strlen(pc);
ptr = new char[len];
strcpy(ptr, pc);
return *this;
}
X& X::Cat(char *pc) {
len += strlen(pc);
strcat(ptr,pc);
return *this;
}
X& X::Copy(X& x) {
Set(x.GetPtr());
return *this;
}
void X::Print() {
cout << ptr << endl;
}
int main() {
X xobj1;
xobj1.Set("abcd")
.Cat("efgh");
xobj1.Print();
X xobj2;
xobj2.Copy(xobj1)
.Cat("ijkl");
xobj2.Print();
}
----------
#include <string>
#include <iostream>
using namespace std;
struct X {
private:
int len;
char *ptr;
public:
int GetLen (X* const THIS) {
return THIS->len;
}
char * GetPtr (X* const THIS) {
return THIS->ptr;
}
X& Set(X* const, char *);
X& Cat(X* const, char *);
X& Copy(X* const, X&);
void Print(X* const);
};
X& X::Set(X* const THIS, char *pc) {
THIS->len = strlen(pc);
THIS->ptr = new char[THIS->len];
strcpy(THIS->ptr, pc);
return *THIS;
}
X& X::Cat(X* const THIS, char *pc) {
THIS->len += strlen(pc);
strcat(THIS->ptr, pc);
return *THIS;
}
X& X::Copy(X* const THIS, X& x) {
THIS->Set(THIS, x.GetPtr(&x));
return *THIS;
}
void X::Print(X* const THIS) {
cout << THIS->ptr << endl;
}
int main() {
X xobj1;
xobj1.Set(&xobj1 , "abcd")
.Cat(&xobj1 , "efgh");
xobj1.Print(&xobj1);
X xobj2;
xobj2.Copy(&xobj2 , xobj1)
.Cat(&xobj2 , "ijkl");
xobj2.Print(&xobj2);
}
Top
93 楼ox_thedarkness()回复于 2006-01-28 15:23:50 得分 5
1
this指针是“每次”调用函数时“传递”给函数的。注意是“每次”。
2
并不保存。 函数内部硬编码访问,参考下面的条款。
3
大多数编译器中,通过ecx寄存器传递。(事实上,这也是一个潜规则了。一般来说,不同编译器都会遵从一致
的传参规则,否则不同编译器产生的obj就无法匹配了)
在call之前,编译器会把对应的对象地址放到eax中。参考条款7
4
每个成员函数(亦即编译器)都认为,自己开始运行的时候ecx保存着一个自己对象的地址。
所有对自己成员变量的访问都是通过这个地址间接访问的——this指针也是一样。
这些都被硬编码到了汇编代码中。
譬如,某个成员函数的这行代码:
a = 1; // 或者 this-> a = 1;
假定a是第二个int元素,偏移为4,那么往往被编码为
mov 4[ecx], 1
但是不要混淆,ecx不一定就是this——比如,编译器认为需要用ecx存放其他数据,例如movs系列指令必须使
用ecx做计数器——这个时候,编译器就可能把ecx push到堆栈中,或者放到另一个寄存器比如edx中。这个时候
上面那行可能就变成了:
mov 4[edx], 1
不管怎么说,编译器始终知道当前对象地址保存在哪个地方,并且硬编码所有访问。
5
这个。。。不予讨论—— 我不认为真的存在this指针。
如果说欺骗编译器的方法的话,参考8。事实上,了解原理之后,想怎么玩编译器都可以。
6
编译期——以及obj文件中,存在一个表指出所有函数的相对入口地址。然而在连接后这个表并不存在于执行文
件中。连接之后,早绑定的函数调用一律变成硬编码call,直接call对应地址。
每个有虚函数的类都包含一个对应的虚函数表。连接之后,虚函数表中的地址也实化(相对于obj而言——obj
给出的都是相对地址,连接之后才有确定地址)这个表是放在执行文件静态数据段的——不过非虚函数地址全部
都没有了。
一个bt的做法是——你可以通过call取到call指令的地址,然后把call指令后面的操作数给还原出来——不过
这个数和实际地址如何对应的就非常复杂了。
7
直接看8给的“解剖——欺骗”代码或许就明白了? 实际解释会很啰嗦。如果还不明白,我下一贴再回答。
8
模拟实现:
A::set 方法访问了A的数据成员;A::output是输出检测。
a是A的一个实例。我们首先看看如何直接调用A::set,并且把a作为this传给该函数;
然后看看 A::mimic_set是如何模拟 A::set 功能,通过ecx访问a的两个成员的。
最后,我们找来一个 int [],把它当作this塞给 A::set,看看又会怎么样。
( VC++7.1 编译。其他编译器原理类似,不过请阅读对应的嵌入汇编指令的说明 )
//====================================
#include <iostream>
class A{
int _i;
int _j;
public:
void set( int i ){_i = i; _j = i*2;}
void output(){ std::cout<<_i<<','<<_j<<std::endl; }
// Mimic the function of A::set()
void mimic_set( int i ){
__asm mov edx, i
__asm mov [ecx], edx // _i = i;
__asm add edx, edx
__asm mov 4[ecx], edx // _j = i*2;
}
};
int main(){
A a;
// 1 模拟调用 a.i()
__asm push 10
__asm lea ecx, a
__asm call A::set
a.output();
// 2 mimic是模仿 a.i功能的一个函数。它的功能和a.i是一样的
a.mimic_set( 20 );
a.output();
// 3 t当然不是a;但是我们能欺骗编译器——他会成功地在t上执行A::i
int t[2];
__asm push 30
__asm lea ecx, t
__asm call A::set
std::cout<<t[0]<<','<<t[1]<<std::endl;
}
//====================================Top
94 楼Muf(沐枫)回复于 2006-01-29 01:00:34 得分 0
谢谢 sjjf(水晶剑锋) 指出我的谬误。
const常量可以有物理存放的空间,因此是可以取地址的。
对被我误导的tx,真的很对不起啊。
新年快乐。Top
95 楼bombwang(王)回复于 2006-01-29 13:42:34 得分 0
learningTop
96 楼feto(酒肉.程序员)回复于 2006-01-29 16:54:29 得分 0
兄弟门看看这个:
class A
{
public:
A():m_a(1)
{
A*const &t = this;
cout<<"Address of this is "<<&t<<endl;
}
int m_a;
};
int main()
{
A obj;
cout<<"Address Of obj :"<<&obj<<endl;
return 0;
}
结果:
Address of this is 0x23ff44
Address Of obj :0x23ff74
编译器 GCC3.4.2
兄弟们新年快乐!Top
97 楼manplus(魅力加加)回复于 2006-01-30 06:42:15 得分 0
markTop
98 楼rigel2001(大宝)回复于 2006-01-30 13:14:31 得分 0
To feto(酒肉.程序员):
//......
A*const &t = this;
cout<<"Address of this is "<<&t<<endl;
//......
事实上你取到的并不是this的地址;
-------------------
如果按照一般的引用赋值的方法,下面这个语句应该对应着两条汇编指令
A*const &t = this;
=>
lea eax,[this]
mov dword ptr[t],eax
这样的话,你的方法就是对的了,但实际上,这条关于this的语句对应着四条汇编指令,
mov eax,dword ptr [this]
mov dword ptr [ebp-20h],eax
lea ecx,[ebp-20h]
mov dword ptr [t],ecx
即你引用的实际上是epb-20h这个指针,它只是对this的一个值拷贝而已,
就是说你根本引用不到this.
Top
99 楼iicup(双杯献酒)回复于 2006-01-30 21:50:20 得分 0
this指针不是全局变量,不是局部变量,
它是成员函数的一个参数.
因此,他只在成员函数中有效.
Top
100 楼feto(酒肉.程序员)回复于 2006-01-31 14:22:11 得分 0
哦,原来是这样,现在我赞同iicpu的观点.Top
101 楼xlsue(小林)回复于 2006-01-31 17:45:51 得分 0
哈,好多语言专家!学习。我觉得this指针没有什么地方值得深入探讨的,在语言底层玩得太精,似乎有点走火入魔的感觉Top
102 楼vollin(林尚义)回复于 2006-01-31 20:47:04 得分 0
:),
大家用的VC7好像有点不同哦,
我在VC7的ctrl_alt_q中得到是可以得到&this的,而且与this的值一样。Top
103 楼icoding(我编码我存在)回复于 2006-02-02 13:25:43 得分 0
牛!Top
104 楼mwg(小刚)回复于 2006-02-03 00:17:43 得分 0
精彩
Top
105 楼cpponduty(饼子村村支书)(出入平安,攻德无量,万受无疆)回复于 2006-02-03 03:23:10 得分 0
this其实就是将对象的地址传递给了对象的成员函数
class A {
void func();
int value;
};
void A::func() {
this->value = 0;
}
A a;
a.func();
struct A {
int value;
};
void func(A* this) {
this->value = 0;
}
A a;
func(&a);
基本等效(不考虑public,private等东西)Top
106 楼ugg(逸学堂(exuetang.net))回复于 2006-02-05 09:11:59 得分 0
本来过年后.打算揭帖.写一篇关于this指针总结.
后来看到大家的讨论,感觉自己写this还是不能深入了解.
惟恐误人子弟,所以暂时不揭帖.
继续讨论Top
107 楼foochow(无聊,灌水......)回复于 2006-02-05 09:37:28 得分 0
呵呵,学习学习!Top
108 楼fiftymetre(50米深蓝)回复于 2006-02-05 13:30:32 得分 0
ugg 的专研精神的确值得我学习的。又懂了不少啊呵呵
回复人: foochow(恰似你的温柔) ( ) 信誉:105 2006-02-05 09:37:00 得分: 0
呵呵,学习学习!
你又死到网吧上网了吧

