一、基础研究

将下面的程序编译连接,用debug加载:

首先执行第一条语句:

发现p=(unsigned char *)0x1000;在这里是把1000赋给一个偏移地址为01af、大小为两字节的内存空间。1000是一个值,这里将它强制转换成unsigned char *型的数据,否则赋给p会因为类型不同而出错。我们知道char型数据应该为1字节,那么为什么这里的数据大小为2字节呢?其实这里的1000是指针p的值,而我们定义的char是*p的值的大小,所以p的大小是两个字节,这个是由什么决定的呢?我们知道两个字节只能存储偏移地址而不能存储段地址,是指针只能指向本段的地址吗?

内存[01af]处的值为1000:

再看第二句是将偏移地址为1000、大小为1字节的内存空间的内容放到al里,为什么这里是地址为1000的空间的内容,而之前就是1000呢?因为之前是(unsigned char *)0x1000,而这里是*(unsigned char *)0x1000,前面的*表示以后面的值为地址的空间的内容。

之后将指针p也就是[1024]的值为偏移地址所指向的内存空间的内容加给al,我们注意到这里用到了bx寄存器来存储p的值,然后用[bx]将*p的值加到al里。

之后将bx清零赋给es,再给bx赋200,那么es:bx就是0:200,即(unsigned char far *)0x200,因为用了far,所以这里用es给出了段地址。之后把0:200地址处一个字节的空间内容加到al里,因为ch是全局变量,所以al只是暂存它的值,之后把al的值赋给ch的空间。

内存[01a8]处的值为7c:

下一句p=&ch;这里将ch的地址01a8赋给指针p的空间,即[01af]。&是取址符。

内存[01af]处的值为01a8:

下一句*p=*p+1;同样用寄存器bx存储p的内容,用al存储*p的内容,执行后内存[01a8]出的值为7d:

下一句pa=&p;对p取址,即对[01af]取址为01af,赋给pa,即[01a6],内存的值为01af;

下一句**pa=**pa+1;这里对pa用了两个*,即先由pa的值指向*pa,又由*pa的值指向**pa,对**pa进行加一操作。我们发现程序把[01a6]赋给bx,再把[bx]赋给bx,即达到两次*的作用,之后[bx]就是操作的对象。操作之后的内存[01a8]的值为7e:

我们发现根据地址值,这里**pa实际上就是*p,*pa实际上就是p,这是为什么呢?因为我们之前将p的地址给了pa所以pa的值实际上就是p的地址,我们用*对pa取值就会得到p的值,而对*pa取值就会得到*p的值。

下一句pf=(char far *)&ch;*pf是一个char far型的指针,对它的操作需要用到段地址。内存空间[01ab]的值为01a8:

下一句*pf=*pf+1;我们发现这里用les指令将内存中从01ab向后的4个字节中的低4位给了bx,高4位给了es。这高4位是哪来的呢?是之前mov [01ad],ds将ds的值保存为了高4位。为什么是ds的值?因为ch存储在本段里。如果ch存储在别的段里,这是是将别的段地址保存吗?

之后的流程和上面*p=*p+1很相似,只是用es:bx代替bx而已。内存空间01a8的值为:

下一句n=(int)&ch;ch的地址为01a8,给了n即[01a9]:

下一句*(char *)n=*(char *)n+1;这里向我们展示了使用指针的另一种方法,即将int型变量用(char *)强制转换成指针变量再来使用。内存空间[01a8]的值为80:

由这个程序我们发现*p就是取出p的内存空间的值为地址的内存空间的值,*是由寄存器bx来完成取值功能的,而&p是取出p的内存空间的地址。

长地址格式的转换形式,远地址格式。强制类型转换,只改变低四位。

再来看下面的程序:

程序中将结构体a的地址给了结构体指针pstu用的是pstu=&a;那么用*pstu=a可以吗?实验发现这样写的话,之后指针都指向错误的地址。因为这里应该是将a的地址赋给pstu,pstu才能指向a,而用*pstu=a则pstu的值没有改变,会指向错误的地址。

在原程序中对结构体指针的使用有两种方法:pstu->number=1和(*pstu).c=80,观察汇编程序可以发现这两种方式的结果是一样的。而bx的值始终为*pstu的值,在调用结构体数据项的时候会以bx+数值的方式访问。

之后我们对char型、int型和stu型的指针+1进行比较。对char型指针,pchar+1执行了一次inc ax语句,对int型指针,pint+1执行了两次inc ax语句,而对stu型指针,pstu+1是执行了add ax,000b语句,也就是将ax的值加了11,而stu所占的内存空间正好是11个字节。所以这里指针+1不是单纯地将指针的值加一,而是将指针的值加一个指针指向的数据类型的大小,也就是指向的地址向后移动一个存储的数据大小。

再来看下一个程序:

首先我们注意到汇编程序是怎么实现for循环的:这里n的地址为01ca,开始现进行一次判断,cmp byte ptr [bx+194],0.194是str的偏移地址相当于str[0],所以这里[bx+194]就相当于str[n]。如果str[n]不为0,则jnz会跳转到语句执行处,将str[n]赋给*(pf+n),之后n加1再进行判断。这里pf的偏移地址为01cc。

之后看下一个for循环的语句pf[n]=*(str+n);发现它的汇编语句与*(pf+n)=str[n]的汇编语句是一样的。也就是说pf[n]与*(pf+n)是等价的。在这样是因为c语言里对指针和数组的操作就是对地址的操作,他们具有相同的性质。

那么((int far *)0x220)[n]就相当于((int far *)0x220+n*2),这里n*2因为(int far *)0x220是一个地址而不是指针变量,而且这个地址的存储单位是int型即2个字节。

同样的观察汇编代码可以发现*(a+n)等价于*(&a[0]+n),即a与&a[0]是等价的。这是因为a[0]是数组的第一项,所以它的地址就是数组a的地址。

所以数组名就是一个指针,[]运算是在指针的值上加上一个偏移量,使其指向下一个存储单元的数据。

二、拓展研究

问题:

1、为什么指针存储地址的空间大小默认是2字节?

答:与编译器有关,16位就是2个字节,32位4个字节,64位8个字节。

2、如果要引用别的段的指针变量,是需要先将别的段的段地址保存,再用les命令吗?

3、指针:一个变量,对应一块内存,指针就是将存储的数据当地址用,而正常的变量将数据当数据用。

三、研究总结

这一章研究了指针机制,指针可以对内存进行操作,是c语言的精髓,研究时应多思考,否则很容易弄糊涂。

从汇编来看c语言之指针的更多相关文章

  1. 从汇编来看c语言之变量

    1.基础研究 对如图程序进行编译连接,再用debug加载. 我们在偏移地址1fa处查看main函数的内容: 执行到1fd处,发现n的偏移地址为01a6,段地址存储在ds寄存器里,为07c4. 再查看函 ...

  2. 从汇编来看c语言

    一. 学习过程 从C语言的角度提出一些问题,这些问题再从汇编的角度考虑,还真的很有意思. (1) 我们用高级语言编程时,一般不可能不用到变量,但是一定要用到变量吗?还有这些变量从汇编的角度是怎么实现的 ...

  3. 从linux0.11中起动部分代码看汇编调用c语言函数

    上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的.在LINUX 0.11中的head.s文件中会看到如下一 ...

  4. [汇编与C语言关系]2. main函数与启动例程

    为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...

  5. C语言二重指针与malloc

    (内容主要源于网上,只是加入了些自己的剖析) 假设有一个二重指针: char **p; 同时有一个指针数组 char *name[4]; 如何引用p呢? 首先我们有程序代码如下 #include &l ...

  6. 从汇编看c++成员函数指针(三)

    前面的从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化. 下 ...

  7. 从汇编看c++成员函数指针(二)

    下面先看一段c++源码: #include <cstdio> using namespace std; class X { public: virtual int get1() { ; } ...

  8. 理解C语言中指针的声明以及复杂声明的语法

    昨天刚把<C程序设计语言>中"指针与数组"章节读完,最终把心中的疑惑彻底解开了.如今记录下我对指针声明的理解.顺便说下怎样在C语言中创建复杂声明以及读懂复杂声明. 本文 ...

  9. C语言中指针和数组

    C语言数组与指针的那些事儿 在C语言中,要说到哪一部分最难搞,首当其冲就是指针,指针永远是个让人又爱又恨的东西,用好了可以事半功倍,用不好,就会有改不完的bug和通不完的宵.但是程序员一般都有一种迷之 ...

随机推荐

  1. [Java] 垃圾回收 ( Garbage Collection ) 的步骤演示

    关于 JVM 垃圾回收机制的基础内容,可参考上一篇博客 垃圾回收机制 ( Garbage Collection ) 简介 上一篇博客,介绍了堆的内存被分为三个部分:年轻代.老年代.永生代.这篇博文将演 ...

  2. CapsLock indicator on Ubuntu for Thinkpad

    http://askubuntu.com/questions/292535/how-to-get-caps-num-scroll-lock-keys-osd-notification sudo add ...

  3. Windows平台下,Scrapy Installation,安装问题解决

    按理说直接:pip install scrapy 就可以成功,但是出现了错误"libxml/xpath.h: No such file or directory" "er ...

  4. [转] Python正则表达式指南

    本文介绍了Python对于正则表达式的支持,包括正则表达式基础以及Python正则表达式标准库的完整介绍及使用示例.本文的内容不包括如何编写高效的正则表达式.如何优化正则表达式,这些主题请查看其他教程 ...

  5. 配置VSFTP服务器

    一.Linux FTP服务器分类:  <1>wu-ftp  <2>proftp=profession ftp  <3>vsftp=very security ftp ...

  6. Linux随笔

    列表:ls -l(显示详细属性)long -a(显示全部文件包括隐藏文件)all -h(字节转换) -d(显示当前目录本身不显示目录下) -i(显示I节点) df -h 人性化查看磁盘占用率 serv ...

  7. python的按位运算

    #coding=utf-8#"&"按位与运算,是指一个数字转化为二进制,然后这些二进制的数按位来进行与运算a=7&18print a'''首先将7转化为二进制,得到 ...

  8. CactiEZ 中文版V10.1安装使用以及139邮箱短信报警设置

    说明:CactiEZ中文版V10.1是基于CentOS 6.0系统,整合Cacti等相关软件,重新编译而成的一个操作系统!   说明:CactiEZ中文版V10.1是基于CentOS 6.0系统,整合 ...

  9. 开发中遇到的java小知识

    今天在优化一个功能的时候遇到了instr与substr函数,之前没有接触过这两个函数,但是今天无意中用到,一查才发现,真是实用的一对兄弟啊. 先来说说substr函数,这个函数返回的是字符串的一部分. ...

  10. DOM---documentFragment

    代码暂时寄存 创建:document.createDocumentFragment() 案例: var a=document.createDocumentFragment(); var b=docum ...