c++ 指针和引用的区别

c++中指针和引用都是非常重要的概念,指针存的是别人的地址,引用呢,是被引用的对象的别名,可以理解为就是被引用对象本身,就像人有大名,有小名,但还是这个人。

关于他们的区别,网上有很多,这里我想通过一个简单的例子来看看他们在汇编代码上的区别,毕竟,程序归根到底都是要到汇编代码,再到二进制的。

$ cat pointer_reference.cpp 
#include <stdio.h>
//g++ -m32 -D PP pointer_reference.cpp
//g++ -m32 -D RR pointer_reference.cpp

#if defined(PP)
void func(int a, int b, int *c)
{
    static int gi = 10;
    *c += a + b;
    printf("%d, %p\n", *c, c);
    c = &gi;
}
#elif defined(RR)
void func(int a, int b, int &c)
{
    static int gi = 10;
    c += a + b;
    printf("%d, %p\n", c, &c);
    c = gi;
}
#endif

int main(int argc, char* argv[])
{
    int a = 1;
    int b = 2;
    int c = 3;
    printf("a:%p,b:%p,c:%p\n", &a,&b,&c);
#if defined(PP)
    func(a, b, &c);
#elif defined(RR)
    func(a, b, c);
#endif
    return 0;
}

运行结果:

$ g++ -m32 -D PP pointer_reference.cpp ; ./a.out
a:0xff8bcd44,b:0xff8bcd48,c:0xff8bcd4c
6, 0xff8bcd4c


$ g++ -m32 -D RR pointer_reference.cpp ; ./a.out
a:0xffb72024,b:0xffb72028,c:0xffb7202c
6, 0xffb7202c


汇编代码:

$ g++ -m32 -D PP pointer_reference.cpp -S -o p.s

$ g++ -m32 -D RR pointer_reference.cpp -S -o r.s

如果有图形化diff工具,会一目了然。

p.sr.s
    .file    "pointer_reference.cpp"
    .section    .rodata
.LC0:
    .string    "%d, %p\n"
    .text
    .globl    _Z4funciiPi
    .type    _Z4funciiPi, @function
_Z4funciiPi:
.LFB0:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    16(%ebp), %eax  => 取c所指向的地址
    movl    (%eax), %eax
=> c所指向的地址存的值
    movl    12(%ebp), %edx => b
    movl    8(%ebp), %ecx =>a
    addl    %ecx, %edx => a+b
    addl    %eax, %edx => a+b+*c
    movl    16(%ebp), %eax
    movl    %edx, (%eax) => 存新的值到c所指的地址
    movl    16(%ebp), %eax
    movl    (%eax), %eax
    movl    16(%ebp), %edx
    movl    %edx, 8(%esp) => c所指的地址压栈
    movl    %eax, 4(%esp) => c所指的地址存的值压栈
    movl    $.LC0, (%esp) => 字符串参数压栈
    call    printf
    movl    $_ZZ4funciiPiE2gi, 16(%ebp) => 更新的是被调函数的局部变量,16(%ebp)是被调函数的形参地址,与它一开始所指向的地址无关
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size    _Z4funciiPi, .-_Z4funciiPi
    .section    .rodata
.LC1:
    .string    "a:%p,b:%p,c:%p\n"
    .text
    .globl    main
    .type    main, @function
main:
.LFB1:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 20(%esp)
    movl    $2, 24(%esp)
    movl    $3, 28(%esp)
    leal    28(%esp), %eax
    movl    %eax, 12(%esp)
    leal    24(%esp), %eax
    movl    %eax, 8(%esp)
    leal    20(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $.LC1, (%esp)
    call    printf
    movl    24(%esp), %edx
    movl    20(%esp), %eax
    leal    28(%esp), %ecx
    movl    %ecx, 8(%esp)
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    _Z4funciiPi
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1:
    .size    main, .-main
    .data
    .align 4
    .type    _ZZ4funciiPiE2gi, @object
    .size    _ZZ4funciiPiE2gi, 4
_ZZ4funciiPiE2gi: => 局部静态变量
    .long    10
    .ident    "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

    .file    "pointer_reference.cpp"
    .section    .rodata
.LC0:
    .string    "%d, %p\n"
    .text
    .globl    _Z4funciiRi
    .type    _Z4funciiRi, @function
_Z4funciiRi:
.LFB0:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    16(%ebp), %eax =>取c的地址
    movl    (%eax), %eax
=> c的值
    movl    12(%ebp), %edx =>b
    movl    8(%ebp), %ecx =>a
    addl    %ecx, %edx =>a+b
    addl    %eax, %edx => a+b+c
    movl    16(%ebp), %eax
    movl    %edx, (%eax) => 更新c的值
    movl    16(%ebp), %eax
    movl    (%eax), %eax
    movl    16(%ebp), %edx
    movl    %edx, 8(%esp) => &c 压栈
    movl    %eax, 4(%esp) => c 压栈
    movl    $.LC0, (%esp)
    call    printf
    movl    _ZZ4funciiRiE2gi, %edx
    movl    16(%ebp), %eax
    movl    %edx, (%eax) => 更新的是16(%ebp)这个地址的值
, 而16(%ebp)存的是被引用变量的地址,所以更新的就是被引用的变量的值
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size    _Z4funciiRi, .-_Z4funciiRi
    .section    .rodata
.LC1:
    .string    "a:%p,b:%p,c:%p\n"
    .text
    .globl    main
    .type    main, @function
main:
.LFB1:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 20(%esp)
    movl    $2, 24(%esp)
    movl    $3, 28(%esp)
    leal    28(%esp), %eax
    movl    %eax, 12(%esp)
    leal    24(%esp), %eax
    movl    %eax, 8(%esp)
    leal    20(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $.LC1, (%esp)
    call    printf
    movl    24(%esp), %edx
    movl    20(%esp), %eax
    leal    28(%esp), %ecx
    movl    %ecx, 8(%esp)
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    _Z4funciiRi
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1:
    .size    main, .-main
    .data
    .align 4
    .type    _ZZ4funciiRiE2gi, @object
    .size    _ZZ4funciiRiE2gi, 4
_ZZ4funciiRiE2gi: => 局部静态变量
    .long    10
    .ident    "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits


































































通过对比指针和引用的汇编代码可以看出,两者的区别很小。

有几个地方值得注意:

1. func函数中的printf,对于指针和引用的汇编代码一样,这个需要好好体会。

引用作为参数传的也是地址,但是直接访问指针的话,访问的是指针所指向的地址,而直接访问引用的话, 会解析该地址上的变量的值。

对引用取地址,则是直接使用该引用参数的值。

2. func函数的形参c,指针和引用的汇编代码相同,这个是Read上的区别。

3. func函数中对c的赋值,体现了指针和引用的重要区别,这是Write上的区别。

对指针赋值,改变的是局部形参的值(该形参的值一开始是被指向变量的地址),与调用函数的那个被指向的变量没有关系;

对引用赋值,改变的是局部形参(该形参是被引用变量的地址)所引用的变量的值,也就是改变了调用函数中被引用变量的值。


---------------------------------------------------------------------------------------------------------------------------


下面的描述综合了网上的一些观点和个人的一些看法:


从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。


而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。


在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:


指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。


而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

个人理解:这个间接寻址是编译器自动完成的,不用在代码级别写出来。也就是说,引用其实也是传地址到形参的局部变量中,但对该局部变量的任何操作都被自动间接寻址到主调函数的实参变量上了。

我们可以改变指针所指向的地址,但我们不能改变引用指向的地址,这是通过编译器来实现这个语言特性的。


引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。


为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:

程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。



最后,总结一下指针和引用的相同点和不同点:

★相同点:

●都是地址的概念;

指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

★不同点:

●指针是一个实体,而引用仅是个别名;

●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;

●引用没有const,指针有const,const的指针不可变;

●引用不能为空,指针可以为空;

●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;

●指针和引用的自增(++)运算意义不一样;

●引用是类型安全的,而指针不是 (引用比指针多了类型检查)


C指针引用区别主要有以下几点: 1. 概念上的区别引用是定义一个变量的别名,而指针是存储一个变量的地址。引用可以看作是已经声明的变量的别名,而指针是存储变量地址的变量。 2. 等级的区别指针可以有多级,也就是可以指向指针指针,而引用只能是一级。 3. 空值的区别指针可以指向空值(nullptr),表示指向一个无效的地址,而引用不能引用空值,它必须引用一个有效的对象。 4. 初始化的区别指针可以在声明时不进行初始化,也可以在后续进行赋值,而引用必须在声明时进行初始化,并且一旦初始化后就不能再改变引用的对象。 5. 操作的区别:由于引用是变量的别名,所以对引用的操作就相当于对原变量的操作,而对指针的操作则需要通过解引用操作符(*)来访问指针所指向的对象。 综上所述,C指针引用在概念、等级、空值、初始化和操作等方面存在着明显的区别。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C++指针引用区别](https://blog.csdn.net/qq_16539009/article/details/120380004)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [c++指针引用区别](https://blog.csdn.net/qq_65139309/article/details/123155326)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值