你还不懂指针吗?带你跳出传统视角,把指针按在地上摩擦

2023-11-22 12:00

本文主要是介绍你还不懂指针吗?带你跳出传统视角,把指针按在地上摩擦,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以下文章来源于程序员入门进阶 ,作者明哥

 

不要陷在指针里面,最好的方法是跳出指针,我们从最终结果来思考问题。于是我的解题思路总是很偏,但是直指本质。

我们写一段代码:

 

编译,反编译,反编译这里我们用objdump -d hello >1.txt,如果你是用IDA,会发现出来的汇编不一样,因为各种格式的汇编,有不同的写法,这里主要就是Intel和AT&T,GNU遵循的是AT&T的写法。

在里面找到我们的add 和main

 

这里我不会展开去讲AT&T,这个玩意就是查表,我这里主要讲几个内容,这个非常重要。汇编语言中自己要关注堆栈平衡,再一个就是寄存器的保存与恢复,第三个就是调用参数约定。

举例来说,add %edx,%eax ,这个的结果在哪里?这个都是指令直接就决定的,也就是我们的CPU设计时候,它的这条指令执行完,数据会放在哪里。

我们看到的main方法中的

mov $0x6,%esi

mov $0x5,%edi

这两个就是我们add方法执行的两个参数,它赋值到这两个寄存器,那么在用这两个寄存器,是不是要把寄存器当前的值保存下来呢?所以你能看到紧挨着上面的就是保存动作。

然后callq 调用add方法,这里我们看紧跟着的 mov %eax ,-0x4(%rbp) ,我们刚才说了add方法执行后,eax里面是结果。

这里将%eax的值放到了 %rbp寄存器-0x4的地方,这个地方是什么?是栈,具体到代码中,就是sum的位置。

int sum =add(5,6);的执行过程就是这样的。我这里分享一个图,主要说的是传参的约定,我们知道调用函数时候是有参数的约定,其实二进制这里也是有的,这个叫做System V ABI 。

 

 

我一般是怎么掌握这些规则,去写汇编,一般就是用C写一些,编译,反汇编来看,这个大家可以参考一本书,

 

我们编译完的程序,是没有sum这个变量,在执行的代码中,都是变成了具体的位置,这里简单说下就是堆还是栈,局部变量是在栈上面,局部静态变量是在堆上面。

全局数据分两类,一类是初始化的全局变量,一类是未初始化的,未初始化的运行时候系统会默认给初始化为0(但是不要以为它就必须是0,这个就是跟运行机制有关,我们写代码一定记住,不要去尝试依赖外部不确定的因素)

全局数据区分为 data rodata 和 bss ,rodata这个就是read only,只读区域这个是由加载器加载程序进入进程时候,会对这个数据区域的page,做设定,设定只读,如果后续在这里写入数据,就会报错。

data就是我们常规的数据,举例就是 int a=100;这类全局变量会放在data区域。而我们如果是int a;这个全局变量,就会放置到bss,这个区域叫做全局未初始化区域,这个跟data的区别在于,这个bss在程序中不占用大小,只是在加载时候会在内存中占用大小。

text区域就是代码段。

说到这里,我这里再说一个内容,我们在看到代码时候,发现printf这个函数,后面有个plt。

 

我们来说下这个plt。plt的意思是,这个方法不在这个程序里面,是在外面的,而对应的位置,这里就是4003f0,这个位置是什么?我们知道printf是在glibc.so ,这里用的动态库。

我们程序要跑起来,是要补全这里的printf的执行块的,系统的做法就是,先放置一个占位位置,然后程序加载的时候,加载器知道这里需要一个printf的真实地址,这些需要放置地址的区域,统一在plt这个区域里面,在这个位置放入真正的printf的入口点。

听起来很绕,我们用一个例子,你就能明白了。但是这里的例子,估计又牵扯进来新的概念,大家先理解下吧。

typedef int (*operate)(int a,int b);这个定义了一个类型,类型是一个有两个参数,一个返回值的函数类型。我们平时的类型就是int,这里是一个函数的类型。

然后声明一个列表,把add,和sub放进来,我们直接调用即可。这里就想给大家说,这个是可以放置一个函数名的,等下我们继续操作,就能够更深入的理解这个函数名。

那么看到这里,我们开始真正进入指针的世界,我们来理解指针。我的操作就是,编译,反汇编,我们先看下代码:

这里我们引入了指针p,储存了变量a的地址,然后*p代表拿出p地址里面的内容,我们看下反编译汇编,分析下这个过程。

这里mov $0x5,-0xc(%rbp)将5放入rbp-0xc的位置。lea -0xc(%rbp),%rax mov %rax,-0x18(%rbp) 这两句话的意思是,拿到 -0xc(%rbp)的地址,放入 -0x18(%rbp)位置,也就是指针p的位置。

这里的mov (%rax),%eax 指的意思是,将rax里面的值读出来,找到这个值对应的地址的内容,存储到%eax里面,这里可以用c语言写就是,int c=*p;

从这里面我想说的就是,我们的指针,这些,在汇编形态下,不过是两种类型,一个是读取寄存器的值,一个是读取把寄存器中的值当做地址对应位置的值。

我们只要这样子去理解,基本上就能清晰的了解指针,指针所存储的值,我们一般都是用它所指向的地址内容,它本身的地址只是途径,类似于我们在图书馆查出来书的序号A-1-303,我们真正要的是这个位置的那本书。

当我们理解了这个,这里的add函数就是个地址,我们这么来看下。

 

我们直接用void *p=add;然后把这个p让编译器按照add对应的参数,返回类型去调用,这样子就可以用到add函数。

int 这类我们就能理解了,那么我们再来说下int[];这个看完汇编语句,一下子就明白了。我们说过一点,就是在真实的计算机上面,执行的是指令,指令理解就两类,一个是值,一个是地址,也可以理解成直接引用,间接引用。

这里想说的是,array在编译器里面,就是理解成一个指针,指向了一个int数组。我们把array赋值到p指针,发现p[1]跟array[1]是一样的。我们看反汇编代码:打印语句改成printf("p[1]=%d,array[1]=%d\n",p[1],array[1]);,来比对下。

这里数组的取值,直接被优化了,直接用的mov -0x1c(%rbp),%edx 从上面的存储可以看到,这个位置直接就是array[1],具体指令是movl $0x2,-0x1c(%rbp),我们指针的获取,这里很明确,拿到数组起始地址,用add %0x4,%rax,进行了偏移,找到了p[1]位置。

这里分享下,地址的+1,指的是地址所指向的内容的大小,进行偏移。理解了这个,再去理解数组,就很好理解了。

我们把代码改成

long *p=array;

printf("p[1]=%d,array[1]=%d\n",(int)(p[1]),array[1]);

打印出来就不一样了,原因就是p+1,是加的sizeof(long) 的大小,也就是它所指向的内容所代表的大小。所以我们再来说下,

int array[3][5]={1,2,3,4,5,

6,7,8,9,10,

11,12,13,14,15};

然后 int (*p)[5] =array; 那么p[1][0]是多少呢?我们前面说了,p+1是依据它指向的大小,这里就是 int [5] 的大小,所以就是输出的6。这里也就是array实际就是一个指向一行五个int的一个数组指针。

核心一句话,指针的+1是根据指向的数据大小决定,同时在指令级别去看,只有两种解析,就是值和地址。

 

如果你对编程感兴趣,想要深入学习。

这里【点我入群】分享素材包及学习资源,还有免费教程哦(包含C语言、C++、Windows、Qt、Linux相关知识点)~不论是小白还是进阶者,在这里都能获得成长。

这篇关于你还不懂指针吗?带你跳出传统视角,把指针按在地上摩擦的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/410002

相关文章

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

Unity3D自带Mouse Look鼠标视角代码解析。

Unity3D自带Mouse Look鼠标视角代码解析。 代码块 代码块语法遵循标准markdown代码,例如: using UnityEngine;using System.Collections;/// MouseLook rotates the transform based on the mouse delta./// Minimum and Maximum values can

C和指针:字符串

字符串、字符和字节 字符串基础 字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。 字符串长度就是字符串中字符数。 size_t strlen( char const *string ); string为指针常量(const修饰string),指向的string是常量不能修改。size_t是无符号数,定义在stddef.h。 #include <stddef.h>

【C++】作用域指针、智能指针、共享指针、弱指针

十、智能指针、共享指针 从上篇文章 【C++】如何用C++创建对象,理解作用域、堆栈、内存分配-CSDN博客 中我们知道,你的对象是创建在栈上还是在堆上,最大的区别就是对象的作用域不一样。所以在C++中,一旦程序进入另外一个作用域,那其他作用域的对象就自动销毁了。这种机制有好有坏。我们可以利用这个机制,比如可以自动化我们的代码,像智能指针、作用域锁(scoped_lock)等都是利用了这种机制。

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

C和指针:结构体(struct)和联合(union)

结构体和联合 结构体 结构体包含一些数据成员,每个成员可能具有不同的类型。 数组的元素长度相同,可以通过下标访问(转换为指针)。但是结构体的成员可能长度不同,所以不能用下标来访问它们。成员有自己的名字,可以通过名字访问成员。 结构声明 在声明结构时,必须列出它包含的所有成员。 struct tag {member-list} variable-list ; 定义一个结构体变量x(包含

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis

Qt: 详细理解delete与deleteLater (避免访问悬空指针导致程序异常终止)

前言 珍爱生命,远离悬空指针。 正文 delete 立即删除:调用 delete 后,对象会立即被销毁,其内存会立即被释放。调用顺序:对象的析构函数会被立即调用,销毁该对象及其子对象。无事件处理:如果在对象销毁过程中还涉及到信号和槽、事件处理等,直接 delete 可能会导致问题,尤其是在对象正在处理事件时。适用场景:适用于在确定对象已经不再被使用的情况下,并且不涉及异步处理或事件循环中的

C语言进阶版第8课—指针(2)

文章目录 1. 数组名的理解2. 指针访问数组3. 一维数组传参本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组 1. 数组名的理解 sizeof(数组名)— 这里的数组名代表整个数组,计算的也是整个数组的大小&数组名 — 这里的数组名代表是整个数组,取出的是整个数组的地址除了以上两种,其他任何地方使用数组名,数组名都表示首元素的地址 //数组名