一、基础研究

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

    {LJ?Dragon}[标题]structs2标签的作用 {LJ?Dragon}[Diary]2017年,愉快的开始:离别不一定总伤感,虽然只是安慰着自己......... 问与答 问题 在Strut ...

  2. 2016最新cocoapods版本更新,以及多个版本的问题

    先删除之前的pod 在终端中执行下面命令. 删除目录下的podrm -rf /usr/local/bin/pod 执行gem list | grep cocoapods 查看所有与cocoapods相 ...

  3. spring注入成员对象

    就是将对象注入到另外一个对象中.这个样例就是有一个学校类,学校类中有一个校长类,最后使用測试文件输出学校类中的信息. 代码结构 学校类 package com.test.SpringGetSet; p ...

  4. Java基础知识强化之IO流笔记12:递归之递归解决问题的思想(图解)

    1. 使用递归计算5!的结果,递归思想的本质如下: 2. 下面就要使用代码实现这个递归: 递归实现分析: (1)做递归要写一个方法 (2)出口条件 (3)规律 代码实现如下: package com. ...

  5. 异步tcp通信——APM.ConsoleDemo

    APM测试 俗话说麻雀虽小,五脏俱全.apm虽然简单,但是可以实现单机高性能消息推送(可以采用redis.kafka等改造成大型分布式消息推送服务器). 测试demo: using System; u ...

  6. Python开发【第十三篇】:jQuery--无内容点击-不进去(一)

    Python开发[第十三篇]:jQuery--无内容点击-不进去(一)

  7. Codeforces-Div312

    题意:给你n个数,进行*2,/2操作,求解最小操作次数能使所有数相同. 思路:因为数值在100000以内,直接枚举过去,对读入的每一个数,模拟操作,用数组s来存放累计操作步数,数组flag用来标记确 ...

  8. python面对对象编程---------6:抽象基类

    抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...

  9. .NET获取机器信息

    /// <summary> /// using System.Web; /// using System.Management; /// </summary> public s ...

  10. JavaScript—window对象使用

    window对象是JavaScript浏览器对象模型中的顶层对象,包含多个常用方法和属性: 1. 打开新窗口 window.open(pageURL,name,parameters) 其中:pageU ...