C/C++ 站在汇编的视角看待引用和指针

这篇文章结合具体的汇编代码,讲解引用和指针的区别。

站在汇编的角度看待指针和引用

在C++中,我们可以使用引用或指针作为函数参数。以下是两种方法的示例:


// 使用引用作为参数
void swap_ref(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 使用指针作为参数
void swap_ptr(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

在这两个函数中,我们都可以交换两个整数的值。但是,使用引用作为参数时,我们可以直接操作变量,而不需要解引用。使用指针作为参数时,我们需要解引用指针才能操作变量。

对于这两个函数的汇编实现,我们可以使用gcc的-S选项来生成汇编代码。以下是上面两个函数对应的汇编实现:


// 使用引用作为参数的汇编实现
swap_ref(int&, int&):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        mov     QWORD PTR [rbp-32], rsi
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
        mov     rax, QWORD PTR [rbp-32]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     DWORD PTR [rax], edx
        mov     rax, QWORD PTR [rbp-32]
        mov     edx, DWORD PTR [rbp-4]
        mov     DWORD PTR [rax], edx
        nop
        pop     rbp
        ret

// 使用指针作为参数的汇编实现
swap_ptr(int*, int*):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        mov     QWORD PTR [rbp-32], rsi
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
        mov     rax, QWORD PTR [rbp-32]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     DWORD PTR [rax], edx
        mov     rax, QWORD PTR [rbp-32]
        mov     edx, DWORD PTR [rbp-4]
        mov     DWORD PTR [rax], edx
        nop
        pop     rbp
        ret

从汇编代码中,我们可以看到两个函数对应的汇编代码是一样的。这是因为在C++中,引用实际上就是一个常量指针。所以,无论我们是使用引用还是指针,底层的实现都是通过地址来访问和修改变量的值。

常量指针

在C++中,引用被设计为对象的一个别名,它就像是对象的另一个名字。一旦一个引用被初始化为一个对象,它就不能被改变为另一个对象的引用。因此,引用总是引用同一个对象。这就是为什么说引用是一个常量指针。

在底层实现上,引用就是一个常量指针。也就是说,它是一个指针,但是你不能改变它的值(即它指向的对象)。这就是为什么你不能改变引用的引用对象,因为这实际上就是在试图改变一个常量指针的值,这是不允许的。

例如,考虑以下代码:


int x = 10;
int& ref = x;

在这里,ref是一个引用,它被初始化为x。在底层,ref实际上是一个指向x的常量指针。因此,你不能改变ref的引用对象,就像你不能改变一个常量指针的值一样。

引用的本质

实际上可以将引用理解为在指针的基础上加了一些限制。在C++中,引用可以被看作是一种特殊的指针,它们都可以用来间接访问变量,但是引用有一些额外的限制:

  • 引用必须在创建时初始化,而指针可以在任何时候初始化。
  • 一旦引用被初始化为一个对象,它就不能被改变为另一个对象的引用。换句话说,引用总是引用同一个对象。而指针可以改变指向。
  • 引用不能为NULL,而指针可以。

这些限制使得引用在某些情况下比指针更安全和更易于使用。

引用的实现是依赖于编译器的。在大多数C++编译器中,引用实际上是通过指针来实现的。当你创建一个引用并初始化它时,编译器在底层创建了一个指针,并将这个指针初始化为指向你指定的对象。然后,每当你使用这个引用时,编译器都会自动解引用这个指针。因此,从这个角度来看,你可以将引用看作是一个自动被解引用的常量指针。

引用不能为空的实现是通过在创建引用时必须进行初始化来实现的。在C++中,你不能创建一个没有初始化的引用。这意味着你不能创建一个引用,然后稍后再让它引用一个对象。引用必须在创建时立即引用一个对象。因此,引用总是引用一个有效的对象,不能为NULL。

为什么C语言只有指针没有引用

C语言没有引用的原因主要是因为C语言在设计时,主要目标是为了系统编程和硬件操作,而不是为了提供高级抽象。C语言提供了指针,可以直接操作内存,这对于系统级编程非常有用。

另一方面,C++引入了引用的概念,主要是为了支持操作符重载和其他高级特性。引用在某些情况下可以使代码更易读和易写,但是它也增加了语言的复杂性。在C语言中,由于没有这些高级特性的需求,因此没有引入引用的概念。

总的来说,C语言没有引用主要是因为它的设计目标和使用场景。C语言主要用于低级编程,直接操作内存,而不需要引用这样的高级抽象。

什么时候使用引用传参数什么时候用指针传递参数?

在C++中,选择使用引用还是指针传递参数主要取决于具体的使用场景和需求。以下是一些考虑因素:

  • 语义清晰:引用在语义上更接近于别名,使用引用可以使代码更易读和易写。而指针则需要考虑解引用和空指针等问题。
  • 安全性:引用在创建时必须被初始化,并且一旦被初始化后就不能改变引用的对象。这使得引用在某些情况下比指针更安全。
  • 功能:指针提供了引用不能提供的功能,例如动态内存分配、指针算术运算、指向指针的指针等。

总的来说,如果你需要修改传入的参数,并且不需要关心参数是否存在(即参数不会是空),那么使用引用可能是更好的选择。如果你需要进行更复杂的内存操作,或者可能需要处理空参数,那么使用指针可能更合适。