《电子科技大学成都学院-错误页面.ppt》由会员分享,可在线阅读,更多相关《电子科技大学成都学院-错误页面.ppt(107页珍藏版)》请在三一文库上搜索。
1、Lecture 03: process control,Zhang Yuhong Computer Science of UESTC,2,指针的定义,为了把数据的地址存储下来,我们必须定义指针。指针是用来存储其它数据的地址的特殊变量。 指针的定义有两种写法: 指针变量类型* 指针变量名称; 指针变量类型 *指针变量名称; 如:float* pF;/定义指针pF (较常用) float *pF;/定义指针pF (较少用),3,指针的定义(续),假设有以下语句: float a=2.5; float* pF;/定义指针变量pF(同样有内存地址,地址中有值) pF=/将数据A的指针存入指针地址pF
2、可以有如下示意图:,4,内存中的数据指针,由于指针本身也是一种数据,因此它在内存中得以调用时,也具备有一般变量的四项特征,对于指针pF而言: 名称: pF (切记不是 *pF) 数据类型: float* 数据内容: 0x0065FE00 内存地址: 0x0065FDFC 特别要注意的是:虽然指针的数据内容都是unsigned int(没有符号的整数),但声明时所指定的目标数据的数据类型(例如: float*)是无法改变的。也是说它只能存储声明时所指定的数据类型的地址。,5,内存中的数据指针(续),例如:int a=2;/A float* pF;/定义指针pF pF=/错误,指针pF必须存放 /
3、float型数据,可改A语句为float a=2.0; 在一般情况下,指针是用来间接存取数据的工具,使用者不需要确实知道它所存储的地址数值。因此我们通常可以将上述指针pF和变量a之间的关系说成“指针pF指向变量A”,如下图所示:,6,指针定义注意的问题,1.如果要同时声明两个以上指针时,必须将语句写成: float *P1,*P2;/同时定义P1,P2两个指针 如果写成: float* P1,P2; 则只用P1被定义成指向float 变量的指针,而P2被定义成一个float 变量。 2. 在定义指针的同时可以给出它的初始值,如: float* pF= /定义指针pF,同时存入数据A的地址。,7
4、,指针定义注意的问题,3.向一个未初始化的指针的内容赋值是非常危险的。当说明一个指针时,编译器并不对该指针做任何初始化工作,因此指针的值是一个随机值。该值所代表的内存单元可能使操作系统正在使用的一个关键的内存单元,若向这个单元写入数据,就有可能导致系统的崩溃。所以类似下列语句是应该绝对禁止的: int *pi,k=5; *pi=k; 也就是说,一个指针只有具有明确的值后才可以对其的内容进行访问。上述问题可以改为:,int *pi,k=5; pi=new int; *pi=k;,int *pi,j , k=5; pi= *pi=k;,或者,8,一维数组,数组是一组相关的存储单元,表现为:这些存储
5、单元具有相同的名字和数据类型。要引用数组的某个特定的存储单元需要说明数组名和该特定元素在数组的序号。 数组占用内存空间。程序员声明了数组元素的类型和个数后,计算机才能为该数组保留合适的内存数量。一般而言,常将数组长度以整数常量(const int)另外声明,或者直接为常数。譬如: const int RecordSize=600; float Temp RecordSize; 注意上述数组长度必须为const int ,不能是变量型,因为数组在编译时就必须确定.,9,一维数组,一维数组的元素可以同过索引(index)或者称之为下标(subscript)的方式指定,以供单独存取。例如: cons
6、t int Size=5; float PSize; 定义一个包含5个元素的float一维数组,就会在内存中分配连续的5个大小32bit的单元,假设P0的地址为0x0065FE00 :如下所示:,P0,P1,P2,P3,P4,0x0065FE00,P,P与P0的地址相同,10,一维数组,P3的开始位置=P0的开始位置+偏移量,也就是说,在计算机内部,下标被用来当作从数组开头起算的偏移量(offset),以存取指定的元素。 Pn的地址= P0的地址+n*sizeof(变量类型) 所以P3的地址: 注:P3念“P sub 3”,11,多维数组,具有多个下标的数组叫做多维数组。多维数组可以看作一维数
7、组的组合,即元素为数组的数组。多维数组的声明方式一般为: 类型 数组名常数 常数 常数常数 通常有n个就称之为n维数组,一般3维以上的数组就很少使用。,12,多维数组,由于多维数组可以看作是一个元素为数组的数组,而数组在内存中是连续存放的,所以多维数组具有下图a的存储形式:,int a22,从图可以看出整个数组a在存储形式上是按一维数组的形式来存储它的各个元素aij;而它的各个元素ai及a本身也是一个数组(指针),因此也是也是按一个一维数组的形式来存储的,其各个元素是aij的。,a b,13,14,15,int P22,16,17,C风格的字符串和标准string类型,1.字符串可以视为由字符
8、(数据类型为char)构成的一维数组,也可以将字符串的首地址给予char指针(又称之为C风格的字符串,C-style string)。因此可以有下列两种声明字符串的方式: char S120=“hello!”;/数组式的声明 char S1=“hello!”;/数组式的声明,省略维数 char *pS2=“hello!”;/指针式的声明,C风格,18,二维字符串数组,字符串数组实际就是一个二维字符数组。例如: char sarr321;/说明了一个包含三个字符串,且每个字符串的最大长度为20的字符串数组。 与多维数组一样,字符串数组也允许进行初始化,如:char sarr321=“C+ lan
9、guage”,”C langguage”,”C+ is better than C”; 图例如下:,19,20,C风格的字符串和标准string类型,2.C风格字符串不方便的地方是,如果看作为字符组的话,必须事先确定该数组的大小,通常造成要么数组太小,不够容纳字符串的字符,要么太大,造成内存空间的浪费。 对于现代的标准string类型来说,是存在这个问题,而且使用非常方便。 例如:string s1;/标准字符串 s1=“abcde”; 使用string必须在头文件纳入string这个头文件,这里string可以类似于一个普通的数据类型来使用。,21,以指针的方式定义字符串,例如:char*
10、ps2=“hi,你好!”; 这个语句在执行的时候完成了两项工作: (1)在内存单元存入字符串“hello,你好”。 (2)将此字符串第一个字符的地址(即h的地址)存入指针ps2中。如下图所示:,22,以指针的方式定义字符串,提示:只有字符串的双引号才可以配合字符串指针的初始化使用。例如: char* pS3=“good luck!”; 或是可以分开成两个语句: char* pS3; pS3 =“good luck!”;/这里不可以理解为指针pS3的内容为字符串“good luck!”。,23,字符串的输出指令,一般而言 int* p =/将变量p内容直接输出 如果x是指针,则输出x的内容地址。
11、 但是上面的这个规则对于字符串指针串指针不适用(string pointer)。,24,字符串的输出指令,char* pS3; pS3 =“good luck!”; printf(“%sn”,PS3);/将pS3指针所指向的地址的内容输出。 这是因为printf在遇到字符串的指针或字符串的起始地址时,会自动将输出改为字符串的内容,而不是将地址输出。 此外,由于字符串都明显的0作为结尾,因此,只有0之前的部分会输出,其后的部分就忽略不计。,25,字符串输输入指令,如果的确想输出某个字符串的地址,需使用特殊的语法: char* pS3; pS3 =“good luck!”; printf(“%sn
12、”, (void *)pS);/将pS3的内容(即一个地址输出)。,26,27,字符串的指针数组,如果把具有相同特征的一群字符串以指针数组来代表,常常可以让字符串的处理更加方便。 参看如下列程序:,28,字符串的指针数组,还可以定义为char* p = 为什么?,解释一下main函数的几个参数,int main ( int argc, char *argv, char *envp ); 1. int argc 表示你在命令行下输入命令的时候,一共有多少个参数。 比方说你的程序编译后,可执行文件是test roothostlocal yhily test 这个时候,argc的值是1 但是如果执行
13、 roothostlocal yhily test myarg1 myarg2 argc的值是3。也就是 命令名 加上两个参数,一共三个参数,29,例如,roothostlocal yhily cp /home/yhily/123.c /home/456.c argc=3 arg表示 argument: The independent variable of a function.函数的自变数 c表示count,30,解释,2. char *argv 用来取得你所输入的参数 roothostlocal yhily test 这个时候,argc的值是1 这个时候,argc的值是1,argv0指向
14、的值是 “test,31,解释,roothostlocal yhily test myarg1 myarg2 这个时候,argc的值是3,argv0指向的值是“test“,argv指向的1的值是“myarg1“,argv 2指向的的值是“myarg2“,argv,32,解释,int main ( int argc, char *argv, char *envp ); 可以改写为: int main ( int argc, char* argv, char* envp );,33,解释char *argv,char* argv 一般用来为程序提供非常重要的信息,如:数据文件名,操作的对象等等。
15、如:cp a.c b.txt 这个时候,a.c和b.txt就是所谓的“非常重要的信息”。不指定这两个文件,你没法进行拷贝。 当程序用到argc和argv这两个参数的时候,可以简单地通过判断argc的值,来看看程序的参数是否符合要求,34,char *envp 相对来说用得比较少。它是用来取得系统的环境变量的。,35,36,37,38,39,什么是进程?什么是程序?,程序是一个包含可以执行代码的文件 ,是一个静态的文件. 进程是一个开始执行但是还没有结束的程序的实例.就是可执行文件的具体实现. 一个程序可能有许多进程,而每一个进程又可以有许多子进程.依次循环 下去,而产生子孙进程. 当程序被系统
16、调用到内存以后,系统会给程序分配一定的资源(内 存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用,40,进程的状态,为了区分各个不同的进程,系统给每一个进程分配了一个ID(就象我们的 身份证)以便识别. 为了充分的利用资源,系统还对进程区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态. 新建表示进程正在被创建 运行是进程正在运行 阻塞是进程正在等待某一个事件发生 就绪是表示系统正在等待CPU来执行命令 完成表示 进程已经结束了系统正在回收资源.,41,42,Process State Transition with Suspend States,43,Linu
17、x进程,Linux进程在内存中包含三部分数据:代码段、堆栈段和数据段。 代码段存放了程序的代码。代码段可以为机器中运行同一程序的数个进程共享。 堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量。 数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。 与代码段不同,如果系统中同时运行多个相同的程序,它们不能使用同一堆栈段和数据段。,44,Process status,1,2,7,9,4,3,6,5,8,返回到 用户态,被抢先,创建,fork,内存不足 仅在对换系统中,内存足够,就绪且换出,唤醒,睡眠且换出,在内存 中睡眠,唤醒,在内
18、存中就绪,换 入,换 出,换 出,睡眠,重新调度 进程,抢先,核心态 运行,返回,系统调用 中断,用户态运行,僵死,退出,中断、中断返回,45,Linux进程状态,Linux进程主要有如下几种状态: 用户运行状态(进程在用户状态下运行的状态) 内核运行状态(进程在内核状态下运行的状态) 内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行) 内存中睡眠(进程正在睡眠并且处于内存中,没有被交换到SWAP设备) 就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行) 睡眠且换出(进程正在睡眠,且被换出内存),46,Linux进程状态,被抢先(进程从内核状态
19、返回用户状态时,内核抢先于它,做了上下文切换,调度了另一个进程,原先这个进程就处于被抢先状态) 创建状态(进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,这个状态是除了进程0以外的所有进程的最初状态) 僵死状态(进程调用exit结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集)。,47,进程控制,进程控制中主要涉及到进程的创建、睡眠和退出等 在Linux中主要提供了fork、exec、clone的进程创建方法 sleep的进程睡眠 exit的进程退出调用 另外Linux还提供了父进程等待子进程结束的系统调用wait。,48,时间片(time slice),内核使用进
20、程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,运行多久以及采用什么特性运行它。 内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。,49,标识号(pid),OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。 进程除了自身的ID外,还有父进程ID(ppid) 所有进程的祖先进程是同一个进程,它叫做init进程,ID为1 init进程是内核自举后的一个启动的进程。init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。,50,pstree命令查看
21、进程,51,ps命令查看进程状态,52,进程号的获取getpid(),进程号的pid可以分别通过函数getpid()获得 父进程号ppid可以分别getppid()获得 在程序中运用这些函数,一定要加上相应的头文件,要加哪些头文件,可以通过man命令查询。,53,54,小技巧,在程序中运用这些函数,一定要加上相应的头文件,要加哪些头文件,可以通过man命令查询。,55,Linux MAN命令,Linux下最通用的领域及其名称及说明如下: 领域 名称 说明 1 用户命令, 可由任何人启动的。 2 系统调用, 即由内核提供的函数。 3 例程, 即库函数。 4 设备, 即/dev目录下的特殊文件。,
22、5 文件格式描述, 例如/etc/passwd。 6 游戏 7 杂项, 例如宏命令包、惯例等。 8 系统管理员工具, 只能由root启动。 9 其他(Linux特定的), 用来存放内核例行程序的文档。,56,57,小技巧2,在vim的命令模式中,把光标移到某个函数中,然后按K(k大写)即可自动查找到对应的函数原型和需要的头文件。例如,58,59,60,1.进程的创建,创建一个进程的系统调用很简单.我们只要调用fork函数就可以了. #include pid_t fork(); 当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方:仅仅子进程ID和父进程ID不同,其它
23、的都是完全一样,即子进程克隆(clone)了父进程.,61,1.进程的创建,当然创建 两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回 值. 当fork调用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用. 对于父进程fork返回子进程的ID,而对于fork子进程返回0. 我们就是根据这个返回值来区分父子进程的.,62,小知识,系统通过进程标识符来区分不同的进程,进程标识符是一个非负正数,他在任何时刻都是唯一的,当某个进程结束时,他的进程标识符可以分配给另外一个新进程。 系统将标识符 0分配给调度进程 标识符1
24、分配给初始化进程。,63,1.进程的创建,父进程为什么要创建子进程? 前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任务就创建子进程来争夺资源. 一旦子进程被创建,父子进程一起从 fork处继续执行,相互竞争系统的资源. 有时候我们希望子进程继续执行,而父进程阻塞直 到子进程完成任务.这个时候我们可以调用wait或者waitpid系统调用.,64,fork Function,pid_t fork(void); The only way a new process is created by the Linux kernel i
25、s when an existing process call the fork function. This function is called once but returns twice. The return value in the parent is the process ID of the new child while the return value in the child is 0. (why?) Both the child and parent continue executing with the instruction that follows the cal
26、l to fork. The child gets a copy of the parents data space, heap, and stack (except little information) (Copy-on-write). In general, we never know if the child starts executing before the parent or vice versa.(尽管Linux内核会有意选择子进程首先执行),65,fork函数返回值示意图,66,小知识,pid_t类型即为进程ID的类型。 事实上,在i386架构上(就是我们一般PC计算机的架
27、构), pid_t类型是和int类型完全兼容的 我们可以用处理整形数的方法去处理pid_t类型的数据,比如,用“%d“把它打印出来。,67,思考一下:输入结果会是怎么样的?,68,for()函数,对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一, 它执行一次却返回两个值,完全“不可思议”。,69,解释1,在语句pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的代码部分完全相同 将要执行的下一条语句都是if(pid=0)。 两个进程中,原先就存在的那个被称作“父进程”,新出现的那个被称作“子进程”。 父子进
28、程的区别除了进程标志符(process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值。 fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值: 在父进程中,fork返回新创建子进程的进程ID; 在子进程中,fork返回0; 如果出现错误,fork返回一个负值;,70,71,解释2,现在就可以看懂剩下的代码了: 如果pid小于0,说明出现了错误; 如果pid=0,就说明fork返回了0,也就说明当前进程是子进程,就去执行printf(“I am the child!“), 否则(else),当前进程就是父进程,执行printf(“I a
29、m the parent!“)。 事实上,两个进程各执行各的,都只有一个条件成立,都各有一条它们永远执行不到的语句。但综合起来给用户的“错觉”是执行了两个判断语句。,72,73,运行结果,74,点评,子进程几乎是当前进程(父进程)的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。 这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,以后相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。 它们再要交互信息时,只有通过进程间(IPC)通信来实现,75,下面的代码有什么样的后果,int main
30、() for(;) fork(); 可能造成系统死机,因为由fork创建的进程如果比父进程结束的晚,就有可能形成僵尸进程,占用系统资源又没有用,造成资源殆尽。 当然只要系统管理员预先给每个用户设置可运行的最大进程数,这个恶意的程序就完成不了企图了。,76,Two uses for fork,When a process wants to duplicate itself so that the parent and child can each execute different section of code at same time. This is common for network
31、server. When a process wants to execute a different program. This is common for shells. In this case the child does an exec right after it return from the fork. Some operating systems combine the operations fork and exec to one function spawn.,77,vfork Function,The function vfork has the same callin
32、g sequence and same return value as fork. But the semantics of the two functions differ. Vfork is intended to create a new process when the prupose of the process is to exec a new program. So vfork creates the new process without fully copying the address space of the parent into the child. While th
33、e child is running, until it calls either exec or exit, the child runs in the address space of the parent. Additionally, vfork guarantees that the child runs first, until the child calls exec or exit.,78,exit Function,There are three way for process to terminate normally and two forms of abnormal te
34、rmination. Executing a return from the main. Calling the exit function. Calling the _exit function. Calling the abort,which generate SIGABRT signal. When the process receive certain signal, which may be cause the process terminate. Regardless how a process terminates, the same code in the kernel is
35、eventually executed. This kernel codes closes all the open descriptors, releases the memory.,79,exec( ),父进程创建子进程后,子进程一般要执行不同的程序.函数族exec( )用来启动另外的进程以取代当前运行的进程。 fork创建一个新的进程就产生了一个新的PID,exec启动一个新程序,替换原有的进程,因此这个新的被exec执行的进程的PID不会改变,和调用exec函数的进程一样。,80,exec( ),为了调用系统程序,我们可以使用系 统调用exec族调用.exec族调用有着5个函数.,81
36、,Relationship of the six exec,execvp,execlp,execl,execle,execv,execve (系统调用),build argv,build argv,Try each PATH prefix,use environ,build argv,82,说明,83,execl(),#include 定义函数: int execl(const char * path,const char * arg,); 函数说明:execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv0、argv1,最后一个参数必须用空指针
37、(NULL)作结束。 返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。,84,范例,85,运行结果,86,范例2,87,运行结果,问题:为什么最后一句代码没有执行?,88,exec运行机理,一个进程一旦调用exec类函数,它本身就“死亡“了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。 (不过exec类函数中有的还允许继承环境变量之类的信息。),89,范例,int execve(const char * filename,
38、char * const argv ,char * const envp ); 函数说明: execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给 执行文件:,90,execlp()函数的使用,91,int execlp(const char * file,const char * arg,); 函数说明: execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv0、argv1,最后一个参数必须用空指针(NULL)作结束。 返回值: 如果执行成功则函数不会返回,执
39、行失败则直接返回-1,失败原因存于errno 中。 错误代码 参考execve()。 范例: /* 执行ls -al /etc/passwd execlp()会依PATH 变量中的/bin找到/bin/ls */ #include main() execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0); ,exec运行机理,如果想启动另一程序的执行但自己仍想继续运行的话,怎么办呢? 那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:,92,93,94,解释,此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。 另
40、外,有一个更简单的执行其它程序的函数system,它是一个较高层的函数,实际上相当于在SHELL环境下执行一条命令,而exec类函数则是低层的系统调用。,95,Memory layout of a Program,Historically, a C program has been composed of the following pieces: Text segment:This is the machine instructions that are executed by the CPU. It is shareable. Initialized data segment. It co
41、ntains variables that are specifically initialized in the program. E.g.: int maxcount = 99; Uninitialized data segment. It is often called the bss segment. Data in this segment is initialized by the kernel to 0 or NULL pointer before the program start executing: long sum1000; Stack. This is where au
42、tomatic variable are stored, along with information that is saved each time a function is called. Heap. Dynamic memory allocation takes place on the heap.,96,Typical memory arrangement,Command-line argument and Environment variables,stack heap,Uninitialized data (bss),Initialized data,text,initializ
43、ed to 0 or NULL by exec,read from program file by exec,High address,Low address,97,exit,exit是第1号调用,其在Linux函数库中的原型是 #include void exit(int status); 不像fork那么难理解,从exit的名字就能看出,这个系统调用是用来终止一个进程的。无论在程序中的什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。请看下面的程序:,98,exit,/* exit_test1.c */ #include
44、 main() printf(“this process will exit!n“); exit(0); printf(“never be displayed!n“); ,编译后运行: $gcc exit_test1.c -o exit_test1 $./exit_test1 this process will exit!,99,分析,程序并没有打印后面的“never be displayed!n“,因为在此之前,在执行到exit(0)时,进程就已经终止了。 exit 系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态 比如说,该进程是正常结束的,还是出现某种
45、意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。,100,exit和_exit,_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构; exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。 exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件, “清理I/O缓冲” 。,101,小知识:缓冲区,在Linux 的标准函数库中,
46、有一套称作“高级I/O”的函数,我们熟知的printf()、fopen()、fread()、fwrite()都在此列,它们也被称作“缓冲I/O(buffered I/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取 每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符n和文件结束符EOF),再将缓冲区中的内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。,102,小知识:缓冲区,如果有一些数据,我们认为已经写入了文件
47、,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内 这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。,103,范例1,* exit2.c */ #include main() printf(“output beginn“); printf(“content in buffer“); exit(0); ,编译并运行: $gcc exit2.c -o exit2 $./exit2 output begin content in buffer,104,范例2,/* _exit1.c * /#include main() printf(“output beginn“); printf(“content in buffer“); _exit(0); 编译并运行: $gcc _exit1.c -o _exit1 $./_exit1 output begin,思考:为什么是这个输出结果? 为什么两个输出结果不一样?,仅仅输出到输出设备的缓冲区了!,105,解析,在Linux中,标准输入和标准输出都是作为文件处理的,虽然是一类特殊的文件 但从程序员的角度来看,它们和硬盘上存储数据的普通文件并没有任何区别。与所有其他文件一样,它们在打开后也有自己的缓冲区。,106,本讲结束,107,
链接地址:https://www.31doc.com/p-2580677.html