如下文章来源于程序员入门进阶 ,做者明哥程序员
C/C++的学习裙【七一二 二八四 七零五 】,不管你是小白仍是进阶者,是想转行仍是想入行均可以来了解一块儿进步一块儿学习!裙内有开发工具,不少干货和技术资料分享!编程
不要陷在指针里面,最好的方法是跳出指针,咱们从最终结果来思考问题。因而个人解题思路老是很偏,可是直指本质。数组
咱们写一段代码:函数
编译,反编译,反编译这里咱们用objdump -d hello >1.txt,若是你是用IDA,会发现出来的汇编不同,由于各类格式的汇编,有不一样的写法,这里主要就是Intel和AT&T,GNU遵循的是AT&T的写法。工具
在里面找到咱们的add 和main学习
这里我不会展开去讲AT&T,这个玩意就是查表,我这里主要讲几个内容,这个很是重要。汇编语言中本身要关注堆栈平衡,再一个就是寄存器的保存与恢复,第三个就是调用参数约定。开发工具
举例来讲,add %edx,%eax ,这个的结果在哪里?这个都是指令直接就决定的,也就是咱们的CPU设计时候,它的这条指令执行完,数据会放在哪里。优化
咱们看到的main方法中的spa
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操做系统,驱动、补丁,图像处理、音视频处理,工业控制软件、嵌入式(手机、智能机)等领域,C++ 已经成为了最受开发人员欢迎的语言之一,巩固了全球的系统和服务。
◆若是你想提高你的编程能力,以便更好从事编程类工做的话!这里为你分享一个学习基地!【点我进入】
分享(源码、项目实战视频、项目笔记,基础入门教程)欢迎转行和学习编程的伙伴,利用更多的资料学习成长比本身琢磨更快哦!