679 views
首页 > 技术心得 > C++ 的指针和引用探秘

C++ 的指针和引用探秘

2009年9月9日

我们在学习 C++ 的时候经常被告诫指针不是引用, 引用不是指针.
今天我们来写一段小程序, 来一探究竟.

1. 关于指针

1#include <stdio.h>
2
3void main( void )
4{
5 int a = 20;
6 int * b = &a;
7 (*b) ++;
8 printf(==== %d ====\r\n, *b);
9}

10

看看我们对它编译后在调试器内的反汇编码:

5: int a = 20;
00401028 mov         dword ptr [ebp-4],14h    ; // 为变量 a 赋值 14h(也就是十进制的 20), [ebp-4] 就代表变量 a
6: int * b = &a;
0040102F   lea         eax,[ebp
-4]                         ; // 取得变量 a 的地址
00401032 mov         dword ptr [ebp-8],eax    ; // 将地址赋值给变量 b; 嗯, 是这个样子, 不离谱
7:        (*b) ++;
00401035 mov         ecx,dword ptr [ebp-8]    ; // [ebp-8] 代表变量 b
00401038 mov         edx,dword ptr [ecx]        ; // 取得指针 b 的值, 存入 edx 寄存器
0040103A   add         edx,
1                                ; // 将 edx 寄存器的值增加 1
0040103D   mov         eax,dword ptr [ebp
-8]
00401040 mov         dword ptr [eax],edx       ; // 将 edx 的值存回 b 指针所指的值
8:        printf(==== %d ====\r\n, *b);
00401042 mov         ecx,dword ptr [ebp-8]
00401045 mov         edx,dword ptr [ecx]
00401047 push        edx
00401048 push        offset string ==== %d ====\r\n (0042801c)
0040104D   call        printf (004012a0)
00401052 add         esp,8
9:    }

以上汇编码跟我们的常识符合.

2. 关于引用

1#include <stdio.h>
2
3void main( void )
4{
5 int a = 20;
6 int & b = a;
7 b ++;
8 printf(==== %d ====\r\n, b);
9}

10

再来看看相应的汇编码:

5: int a = 20;
00401028 mov         dword ptr [ebp-4],14h
6: int & b = a;
0040102F   lea         eax,[ebp
-4]
00401032 mov         dword ptr [ebp-8],eax
7:        b ++;
00401035 mov         ecx,dword ptr [ebp-8]
00401038 mov         edx,dword ptr [ecx]
0040103A   add         edx,
1
0040103D   mov         eax,dword ptr [ebp
-8]
00401040 mov         dword ptr [eax],edx
8:        printf(==== %d ====\r\n, b);
00401042 mov         ecx,dword ptr [ebp-8]
00401045 mov         edx,dword ptr [ecx]
00401047 push        edx
00401048 push        offset string ==== %d ====\r\n (0042801c)
0040104D   call        printf (004012a0)
00401052 add         esp,8
9:    }

可以看出, 这两段汇编码完全一样.

因此, 可以下结论了, 在汇编层面, 引用和指针是完全一样的东西. 咱们的教科书上的说法仅仅是语言层面的, 编译器的底层实现是有自己的逻辑的.

================= 风华绝代华丽无比的分割线 ===========================
PS:
今天看了 dophi 童鞋的评论, 又参考了网上的其他文章, 说点题外话. 经过试验, 数组的引用是不能作为函数的参数的, 例子如下:

// 如果定义成如下这样, 将不能通过编译
void RefAsParam(int & aRR[])
{
printf(
==== %d  == %d ====\r\n, aRR[0], sizeof(aRR));
}

甚至也不能定义成这样:

void RefAsParam(int & aRR[10])
{
printf(
==== %d  == %d ====\r\n, aRR[0], sizeof(aRR));
}

而只能定义成这样:

void RefAsParam(int aRR[])
{
printf(“==== %d  == %d ====\r\n”, aRR[0], sizeof(aRR));
}

或者这样 void RefAsParam(int aRR[10]), 但其中的sizeof(aRR) 的值等于4, 即一个指针的大小, 而不是 4*10=40 个字节.
能通过编译且sizeof(aRR)等于 40 的的代码列出如下, 这定义的新类型定死了就是 10 个元素的数组, 相当没有灵活性.

#include <stdio.h>

// 只能定义成这样, 一个新类型, 含10个元素的整型数组
typedef int MyArray[10];

void RefAsParam(MyArray & aRR)
{
printf(
==== %d  == %d ====\r\n, aRR[0], sizeof(aRR));
}


void main( void )
{
int a[10] = { 20, 30, 77 }; // 或者像这样:  MyArray a = {20, 30, 77};
RefAsParam(a);
}

现在, 我们为了元素类型和元素个数的灵活性, 只能玩玩模板了, 看看下面:

#include <stdio.h>

template
<typename elemType, size_t nSize>
class CRefAsParam
{
public:
typedef elemType MyArray[nSize];

// 当然, 本函数也可以定义成形如: static const void RefAsParam(const MyArray & aRR)
static void RefAsParam(MyArray & aRR)
{
printf(
==== %d  == %d ====\r\n, aRR[0], sizeof(aRR));
}

}
;

void main( void )
{
typedef CRefAsParam
<int, 10> MyType;

MyType::MyArray a
= {20, 30, 77};
MyType::RefAsParam(a);
}

这样一来, sizeof(aRR) 值是达到 40 了, 但咱们也玩了相当的奇技淫巧. 跟我们追求的简单,直接,有效简直差了十万八千里.哎~~~

PPS, 我上面犯了一个错误, 有人纠正了, 看下面的评论:

# re: C++ 的指针和引用探秘 2009-09-10 15:39 | nelson
void RefAsParam(int (& aRR)[10]);

数组的引用作函数入参要这样…… 回复 更多评论
删除评论
# re: C++ 的指针和引用探秘 2009-09-10 15:43 | nelson
#include
using namespace std;

void RefAsParam(int (&aRR)[5])
{
cout << aRR[3] << endl;
}

template < size_t SZ >
void RefAsParamT(int (&aRR)[SZ])
{
cout << aRR[0] << endl;
}

void main()
{
int a[5] = { 1, 2, 3, 4, 5};
RefAsParam( a );
RefAsParamT( a );
getchar();
}

我觉得这里用模板的好处是,在函数声明、定义的时候无需对数组引用的大小作限制,调用RefAsParamT( a )时隐式地将非类型参数实例化了,用着比较爽 回复 更多评论
删除评论
# re: C++ 的指针和引用探秘 2009-09-10 15:46 | free2000fly
@nelson
谢谢, 兄弟高招. 也找到关于这个问题的一个网页了:

http://tech.e800.com.cn/articles/2009/810/1249873457464_1.html

前述函数甚至可以改成这个样子:
template < typename elemType, size_t SZ >
void RefAsParamT(elemType (& aRR)[SZ])
{
cout << aRR[0] << “====” << sizeof(aRR) << endl;
}
但本例及 nelson 兄弟的例子都不能在 VC6 上通过编译, 在其它如 VS2003 及以上无碍. 回复 更多评论

技术心得

  1. 目前还没有任何评论.
  1. 目前还没有任何 trackbacks 和 pingbacks.