IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 通过汇编理解 C 语言指针原理 -> 正文阅读

[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++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 20:56:54  更:2022-10-22 21:01:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 7:06:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码