一、基础研究

我们之前研究过变量、数组、函数和指针,他们都可以看作是内存中存储的一段数据,当程序需要用到它们时,会通过它们的地址找到它们并进行调用,只是调用的用途不同而已:变量和数组元素是作为常量来处理,对它们进行赋值、运算、取址等操作,而程序是从首地址开始执行直到返回,指针是用来对地址进行操作,或者对指向的内容进行操作。但是我们要知道,它们在内存中都是以一个字节一个字节的数据形式存储的,我们可将他们的存储空间都看作是一个char型数组。

现在定义了一个有200个元素的char型数组a,要我们向a中加入数组元素,使程序可以在屏幕中间打印一个字符“c”。在执行程序main里只有一句语句:((void (far *)())(long)a)();分析语句:a是数组的首地址,将它强制转换成long型使它的数据包含段地址和偏移地址的数据,但此时它还不是一个地址而是一个long型变量,那么我们将它再强制类型转换成一个void(far *)()型的函数指针,它是一个远指针,指向一个void型的函数,所以这个函数的入口地址就是数组a的首地址。即程序是要执行以数组a里的元素构成的语句所组成的函数。那么我们要向数组a里填充的是一段内存空间的数据,这些数据连起来能被翻译成一段语句,这段语句的功能是在屏幕中间打印一个字符“c”。

那么我们先考虑怎么在屏幕中间打印一个字符“c”。我们知道输出函数都是在当前光标的位置输出,而我们要在屏幕中间打印是在固定位置输出,即在段地址为b800的数据段的某一个位置存放要输出的数据,我们知道dos窗口的大小是80*25的,一共占4000个字节,那么屏幕中间是,即偏移地址为:(13*80+40-1)*2=2158=0x86e,那么要打印的地址为0xb800086e。我们怎么把字符c放到内存中地址0xb800086e处呢?要对地址操作首先想到的就是指针,因为要存放段地址加偏移地址,所以要定义一个far指针。我们先写一个输出的程序如下:

输出结果为:

可见结果是正确的。现在我们要找到这个程序在内存中存储的数据,我们用debug加载程序:

可以看到,程序从01fa开始,到0215结束,我们查看这一段的内存:

可见这一段的内存里的数据为55 8b ec 83 ec 04 c7 46 fe 00 b8 c7 46 fc 6e 08 c4 5e fc 26 c6 07 63 8b e5 5d c3,我们将这些数据存到数组a中,看能否打印出c,结果发现虽然输出了c,但是还在屏幕上输出了两个字符,还有程序输出了之后没有正确返回:

但是查看内存,我们向数组a里补充的数据完全正确,用u命令查看也发现和我们之前写的输出语句的汇编语句一样,那是为什么呢?我们之前写的输出语句是在main函数里输出的,但是这里数组a是在数据段里的,不是在一个段里,需要返回值,所以我们在输出函数里把输出语句写在一个far型的子函数里,程序如下:

查看函数f的汇编语句为:

可以发现最后的返回语句变成了retf,再查看内存空间:

只有21e处的c3变成了cb,修改之后发现能够正常显示:

所以我们在写程序要转换程序和数据时一定要注意这个程序的位置,它的数据能否直接移植到其他程序里面使用。还有一定要注意,我们将一个数组的首地址强制转换成函数指针时,一定要先将它强制转换成long型数据,这样它才能包含段地址和偏移地址,函数指针才能正确指向到函数。

二、扩展研究

到现在为止我们已经学过了c语言一些比较重要的也是主要的部分:变量、数组、函数、指针,我们还了解了一些编译原理和编译器的命令,现在来总结一下:

变量:

变量是一种存放数据方式,与常量不同,它的内容是可以改变的。它可以分为全局变量和局部变量,它们的本质区别是存储的位置不同,全局变量是在内存中存储的,而局部变量是在栈段中存储的,这个差别导致了它们的一系列区别:全局变量存储的内存空间是没有内存对齐的情况的,而局部变量有;全局变量作为参数传递是直接用地址调用,而局部变量是入栈的方式;全局变量的生命周期是整个程序,而局部变量的生命周期是当前函数;全局变量的段地址在ds寄存器里,局部变量的段地址在ss寄存器里;全局变量定义是自动清零的,而局部变量定义时在栈中的空间还是原来的数据。比较特别的静态局部变量的存储位置和生命周期都和全局变量一样,只是静态局部变量只能在定义的函数中使用。

比较重要的变量类型有char、int、long、double和结构体,它们分别占的大小为1字节、2字节、4字节、8字节,结构体的大小是结构体中数据项之和。结构体也存在内存对齐的情况,结构体中各数据项存储位置是相邻的。结构体作为参数传递和返回比一般变量要复杂,一般变量都是直接入栈,而结构体必须创建一个临时变量,用块搬移函数将结构体的各数据项复制到临时变量里,在子函数里再将临时变量的值搬移到栈段里面,返回的原理也是相同的。

数组:数组是利用一段连续的内存空间存放一系列相同类型的数据。一维数组是存储的数据按照线性的顺序来排列,二位数组是存储的数据按照网状的顺序来排列,多维数组是存储的数据以多维的形式存储,我们可以通过当前哪一组不断向下查找到某一个元素。数组也可以根据存放的元素类型不同来分类:整型数组的元素是int型数据、指针型数组的元素是指针型数据、结构体数组的元素是结构体数据。数组中的元素是连续存放的。数组名相当于数组的首地址,也是数组第一个元素的地址,它的使用和指针有相似之处,如果p是一个指针,那么p[n]等同于*(p+n),即跳到下一个元素就相当于在当前地址上加上数组的类型大小。数组还有函数指针数组,存放的元素是函数指针,指向函数指针,函数指针数组可以将要运行的程序以数据的形式写入并对函数进行调用。

函数:函数是一段语句的集合。函数名相当于一个函数指针,存储函数的入口地址,程序由这个入口地址跳转到当前函数。函数的参数是局部变量,在调用该函数的函数中将参数压入栈中,在子函数里用bp寄存器找到参数的地址进行调用。函数可以有返回值,void函数没有返回值,函数的返回值一般是存储在寄存器中,如果返回值为结构体,则将结构体的内容传递到一个临时变量里。函数是一段数据,它同样存储在内存空间里,这样我们可以以数据的形式将一个函数写到内存中执行。

指针:指针存储的数据是一个地址,我们可以通过“*”来取得指针存储的这个地址处的内容,通过“&”来取得一个内存空间的地址赋给指针。指针加减一个数并不是以它的值加减一个数字,而是加减它所指向的存储空间的数据类型的大小,即如果它指向的是int型数据,那么加1就是在当前地址上加上2个字节。我们可以将一个地址赋给一个整形变量,但我们不能对一个整形变量使用“*”取得它所存储的地址处的值,因为它不是一个指针,同时如果一个指针是一级指针,即定义成*p,那么只能用“*”对它取一次值,如果一个指针是二级指针,可以用“*”对它取两次值,总之,一个指针是几级指针,就可以对它取几次值。对于指针的使用我们一定要注意它和其他的变量的类型匹配问题,近指针占2个字节,存储偏移地址,远指针占4个字节,存储段地址加偏移地址,我们要用%p输出近指针的值,用%Fp输出远指针的值。虽然指针的值的大小是固定的,但是指针指向的值的大小和指针的定义有关。指针在地址和内容之间建立了一条联系,这种联系是c语言最重要的基础,我们可以用它来实现多种数据结构。指针可以指向任意的数据类型,结构体指针指向的是一个结构体,它可以以->符号调用结构体的数据项。函数指针是指向一个函数入口的指针,当定义一个函数指针时要指明函数的类型和参数类型和个数,通过函数指针可以调用指定位置的函数。

三、研究总结

c语言十分精简也十分快捷,它的优点是建立在用户可以直接对内存进行操作的基础上,这样用户实现一种功能可以有很多种写法,可以说是能够充分发挥用户的想象力和创造性,但是这样也有缺点,就是错误可能很多。我们需要将这些知识融合起来,融合的最好的方式就是多写程序,发挥自己的想象力和创造性,对于一个问题,思考多种解决办法,如果碰到了问题,要仔细思考问题在哪里,这样才能够提高。对于有问题的地方,不要随便问问题,要自己先写程序来试验自己的猜想。

用c语言程序对显存进行操作的更多相关文章

  1. Ubuntu-Tensorflow 程序结束掉GPU显存没有释放的问题

    笔者在ubuntu上跑Tensorflow的程序的时候,中途使用了Win+C键结束了程序的进行,但是GPU的显存却显示没有释放,一直处于被占用状态. 使用命令 nvidia-smi 显示如下 两个GP ...

  2. CUDA 显存操作:CUDA支持的C++11

    CUDA9的编译器和语言改进 使用CUDA 9,nvcc编译器增加了对C ++ 14的支持,其中包括新功能 通用的lambda表达式,其中使用auto关键字代替参数类型; auto lambda = ...

  3. 第一个C语言程序

    从第一个C语言程序了解C语言 了解关键字 了解函数 注释 C语言的执行流程 标识符 C语言的学习重难点 从第一个C语言程序了解C语言 上图是一个在控制台上显示“Hello, World!”的C语言源代 ...

  4. linux终端下 编译c语言程序

    linux终端下,编译C语言程序步骤为: 采用vi进行源代码编写,编写完成后,:wq存盘退出,如: vi test.c 在命令行下,运行gcc编译程序,生成执行码,如: gcc  -o test te ...

  5. Go语言程序的状态监控 via 达达

    Go语言程序的状态监控 Go是很实在的编程语言,从一开始就提供了很详细的运行状态信息.产品上线后的调优和排查疑难杂症都得靠这些状态信息.这边总结一些我们项目里用到的状态监控手段. pprof Go自带 ...

  6. [置顶] 基于FPGA的VGA简易显存设计&NIOS ii软核接入

    项目简介 本项目基于Altera公司的Cyclone IV型芯片,利用NIOS II软核,2-port RAM与时序控制模块,实现64*48分辨率的显存(再大的显存板载资源m9k不够用) 实现效果如下 ...

  7. Android For JNI(一)——JNI的概念以及C语言开发工具dev-c++,编写你的第一个C语言程序,使用C启动JAVA程序

    Android For JNI(一)--JNI的概念以及C语言开发工具dev-c++,编写你的第一个C语言程序 当你的Android之旅一步步的深入的时候,你其实会发现,很多东西都必须去和framew ...

  8. (原)tensorflow中使用指定的GPU及GPU显存

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/6591923.html 参考网址: http://stackoverflow.com/questions ...

  9. ubuntu服务器常见使用技巧及-kill掉后GPU显存不释放进程-

    如何解决python进程被kill掉后GPU显存不释放的问题 1 重新开一个shell,然后输入: ps aux|grep user_name|grep python.所有该用户下的python程序就 ...

随机推荐

  1. Drawer_layout 关闭滑动视图

    在android抽屉Drawer_layout开发中,我需要关闭滑动的试图 找到了这个方法 mDrawer_layout.setDrawerLockMode(DrawerLayout.LOCK_MOD ...

  2. ios中键值编码kvc和键值监听kvo的特性及详解

    总结: kvc键值编码  1.就是在oc中可以对属性进行动态读写(以往都是自己赋值属性)           2. 如果方法属性的关键字和需要数据中的关键字相同的话                  ...

  3. MongoDB 聚合

    聚合操作过程中的数据记录和计算结果返回.聚合操作分组值从多个文档,并可以执行各种操作,分组数据返回单个结果.在SQL COUNT(*)和group by 相当于MongoDB的聚集. aggregat ...

  4. 关于这两天研究Java打印pdf方法的记录

    这两天在研究Java调用打印机打印PDF文件的方法,学到了不少东西,特别来记录一下. 关于Java打印网上最多的而且也是Java正统的打印方法就是使用PrintService,一套比較标准的打印代码例 ...

  5. 第一篇:K-近邻分类算法原理分析与代码实现

    前言 本文介绍机器学习分类算法中的K-近邻算法并给出伪代码与Python代码实现. 算法原理 首先获取训练集中与目标对象距离最近的k个对象,然后再获取这k个对象的分类标签,求出其中出现频数最大的标签. ...

  6. [转] 有趣的JavaScript原生数组函数

    在JavaScript中,可以通过两种方式创建数组,Array构造函数和 [] 便捷方式, 其中后者为首选方法.数组对象继承自Object.prototype,对数组执行typeof操作符返回‘obj ...

  7. [转] nodeJS的post提交简单实现

    index.js: ? 1 2 3 4 5 6 7 8 var server = require('./server'); var router = require('./route'); var r ...

  8. MVC使用Exception过滤器自定义处理Action的的异常

    1.继承FilterAttribute ,IExceptionFilter自定义处理 /// <summary> /// 登录错误自定义处理 /// </summary> pu ...

  9. c#中去掉字符串空格方法

    (1)Trim方法 string   tt=" aaa "; tt=tt.Trim()       去字符串首尾空格的函数 tt=tt.TrimEnd() 去掉字符串尾空格 tt= ...

  10. Oracle创建表空间以及用户,并授权

    //创建临时表空间   create temporary tablespace testtemp  tempfile 'D:/app/Administrator/oradata/testdata/te ...