通过汇编理解 C 语言指针原理

通过汇编理解 C 语言指针原理指针也就是内存地址,指针变量是用来存放内存地址的变量

指针用法

指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var_name;
#include <stdio.h>
int main ()
{ 
   
    int var_runoob = 10;
    int *p;              // 定义指针变量
    p = &var_runoob;
    printf("var_runoob 变量的地址: %p\n", p);
    return 0;
}

/* * output: * var_runoob 变量的地址: 0x7ffeeaae08d8 **/
#include<stdio.h>
int main(){ 
   
    int var = 20; // int 类型变量声明
    int *ip;      // 指针变量声明
    ip = &var;    // 指针变量 ip 中保存 var 的地址
    printf("%p", &var); // var 变量的地址
    printf("%p", ip);   // ip 中保存的地址
    printf("%d", *ip);  // 使用指针访问值
}

/* * output: * 0x7fff1284c344 * 0x7fff1284c344 * 20 * **/

通过汇编理解指针

// demo.c
#include<stdio.h>
int main(){ 
   
    int var = 20; // int 类型变量声明
    int *ip;      // 指针变量声明
    ip = &var;    // 指针变量 ip 中保存 var 的地址
    printf("%p", &var); // var 变量的地址
    printf("%p", ip);   // ip 中保存的地址
    printf("%d", *ip);  // 使用指针访问值
}

编译 demo.c

// GCC: (GNU) 11.2.1 20220127 (Red Hat 11.2.1-9)
gcc -S demo.c 

查看 demo.s(为了便于理解未开启优化且删除了部分和本文要讨论的内容无关代码)

main:
    pushq    %rbp
    movq    %rsp, %rbp      // 开辟新的栈帧
    subq    $16, %rsp       // 栈上开辟 16byte
    
    // int var = 20;
    movl    $20, -12(%rbp)  // 将 4byte 大小的立即数 20 放入栈中
    
    // int *p; ip = &var;
    leaq    -12(%rbp), %rax // 将立即数 20 在栈中的内存地址取出,放入 RAX 寄存器中
    movq    %rax, -8(%rbp)  // 将 RAX 寄存器中的地址放入栈中
    
    // printf("%p", &var);
    leaq    -12(%rbp), %rax // 将立即数 20 在栈中的内存地址取出,放入 RAX 寄存器中
    movq    %rax, %rsi      // 将立即数 20 在栈中的内存地址放入 RSI 寄存器中
    movl    $.LC0, %edi     // 将 printf 使用的格式化字符串地址放入 EDI 寄存器中
    movl    $0, %eax        // 将 0 放入 EAX 寄存器中
    call    printf          // 调用 printf 输出立即数 20 的地址

    // printf("%p", ip);
    movq    -8(%rbp), %rax 
    movq    %rax, %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf          

    // printf("%d", *ip);
    movq    -8(%rbp), %rax
    movl    (%rax), %eax
    movl    %eax, %esi
    movl    $.LC1, %edi
    movl    $0, %eax
    call    printf
    
    movl    $0, %eax
    leave
    ret

根据上述汇编代码,我们总结以下什么是指针:

  • 指针就是一个内存单元中保存了一个地址。
  • 在 C 语言中使用 & 地址符可以获取这个地址,相当于汇编的 lea 指令。
  • 在 C 语言中使用 * 解地址符号,访问内存地址中的变量信息,相当于汇编中的 ()

下面我们再看一个例子加深理解

#include <stdio.h>
void sum(int *p){ 
   
    *p=3;
}

int main(void)
{ 
   
    int shareData=1;
    sum(&shareData);
    return 1;
}
// GCC: (GNU) 11.2.1 20220127 (Red Hat 11.2.1-9)
gcc -S demo.c 
sum:
    pushq    %rbp
    movq    %rsp, %rbp
    
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax

    // *p = 3; * 相当于 ()
    movl    $3, (%rax) // 将立即数 3 放入 rax 寄存器寻址的内存地址中
    nop
    popq    %rbp
    ret
main:
    pushq    %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp // 开辟内存

    // int shareData=1;
    movl    $1, -4(%rbp) // 把立即数 1 放入 RBP 寄存器寻址的内存地址减 4 字节的地址中
    
    // sum(&shareData) 使用 C 语言的地址符 & 抽象汇编中的 lea 取地址指令 
    leaq    -4(%rbp), %rax // 获取立即数 1 的地址,放入 RAX 寄存器中
    movq    %rax, %rdi
    call    sum
    
    movl    $1, %eax
    leave
    ret

指针的算数运算

C 语言中,对指针执行 +1 运算,编译器会参照指针类型的大小,执行+1。如果是 int 类型的指针,对其 +1,编译器就会自动加上 int 类型大小的长度,即 4 字节。

#include<stdio.h>
int main(){ 
   
    int a[] = { 
   1,2,3,4};
    printf("%p", a); // 等价于 printf("%p", &a[0]);
    printf("%p", a+1); // 等价于 printf("%p", &a[1]);
}

/* * output: * 0019FF20 * 0019FF24 * **/

数组

#include <stdio.h>
int main(void){ 
   
    int arr[3] = { 
   1,2,3}; // int 占 4 字节,使用 esp 开辟 12 字节空间
    int a = arr[0]; // 开辟 4 字节
    int b = arr[1]; // 开辟 4 字节
    int c = arr[2]; // 开辟 4 字节
    return 1;       // 总共需要栈内空间(指令片段的私有数据) 24 字节
}
gcc -S -m32 demo.c // 32 位架构编译
main:
    // 开辟栈帧
    pushl %ebp
    movl %esp, %ebp 
    
    // int arr[3] = {1,2,3};
    subl $32, %esp       // 开辟 32 字节内存(内存对齐) 
    movl $1, -24(%ebp)   // 将 1 放入栈中
    movl $2, -20(%ebp)   // 将 2 放入栈中
    movl $3, -16(%ebp)   // 将 3 放入栈中
    
    // intel 规定,不允许在两个内存中互相传递数据
    // 所以这里用 EAX 寄存器进行中转
    
    // int a = arr[0];
    movl -24(%ebp), %eax  // 将栈中的 1 传入 EAX 寄存器中
    movl %eax, -4(%ebp)   // int a = 1;
    
    // 从上面两行代码可推理得:arr[0] 等价于 -24(%ebp)
    // 继而推理得:C 语言中的 arr[n] 等价于汇编中的括号 ()
    
    // int b = arr[1];
    movl -20(%ebp), %eax
    movl %eax, -8(%ebp)    
    
    // int c = arr[2];
    movl -16(%ebp), %eax
    movl %eax, -12(%ebp)   
    
    movl $1, %eax
    leave
    ret

根据上述汇编代码,我们总结指针与数组的关系:

  • C 语言中的 arr[n] 等价于汇编中的括号 ()

指针获取数组元素

推理

由上文可知在 C 语言中使用 * 解地址符号,访问内存地址中的变量信息,相当于汇编中的 ()
C 语言中的 arr[n] 等价于汇编中的括号 ()
所以我们推测可以通过指针操作访问数组,即使用 *(a) 代替 a[0] 访问数组元素。

验证

用指针获取数组元素

#include<stdio.h>
int main(){ 
   
    int a[] = { 
   1,2,3,4};

    printf("%d", *a);
    printf("%d", *(a+1));    
}

/** * output: * 1 * 2 **/

二级指针

指向指针的指针。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

指针数组

// ptr 声明了一个 COUNT 个元素的数组,数组中每个元素都是指向 int 类型的指针。
int *ptr[COUNT];
#include<stdio.h>
const int MAX = 3;
int main{ 
   
    int var[] = { 
   2, 43, 212};
    int i, *ptr[3];
    for(i = 0; i < MAX; i++){ 
   
        ptr[i] = &var[i]; // ptr 数组中保存 var 数组中元素的地址
    }
    for(i = 0; i < MAX; i++){ 
   
        printf("var[%d] = %d", i, *ptr[i]); // 使用 * 解地址,获取地址中的值。
    }
    return 0;
}

/* * output: * var[0] = 2 * var[1] = 43 * var[2] = 212 * */

参考资料

  • 菜鸟
  • 《深入理解 JAVA 高并发编程》

今天的文章通过汇编理解 C 语言指针原理分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/67255.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注