Linux-Unix I/O 和 ANSI I/O 有什么区别,该和何时使用他们中的哪些I/O函数呢?

Linux-Unix I/O 和 ANSI I/O 有什么区别,该和何时使用他们中的哪些I/O函数呢?

虐人心 发布于 2017-08-02 字数 200 浏览 1121 回复 2

Unix I/O 和 ANSI I/O 有什么区别,该和何时使用他们中的哪些I/O函数呢?
另外还有两个小问题:
1. 跟在输出函数之后的输入函数
2. 跟在输入函数之后的输出函数
分别会产生什么问题?

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

晚风撩人 2017-10-21 2 楼

UNIX I/O指的是:open,read,write,lseek,close函数。这里read和write是不带缓冲的I/O,它们分别对应于内核中的一个系统调用。
ANSI I/O指的是:fopen,fread,fwrite,fseek,fclose函数,也称为标准I/O。这里fread和fwrite默认是带缓冲的(缓冲区大小是4096字节)。使用标准I/O的好处很明显,首先是提高了程序运行效率(在全缓冲的情况下,缓冲区填满后才会调用一次系统调用,减少了内核切换带来的消耗),其次调用的应用程序可以更好的跨平台(有没有发现fread在Linux和windows都能调用)。

至于LZ提到的:
“1. 跟在输出函数之后的输入函数
2. 跟在输入函数之后的输出函数”

直接看下面的代码就会有深刻的体会:

#include <stdio.h>

int main()
{
FILE *testFile = NULL;
int ret = 0;
char string[200] = {''};

testFile=fopen("test1.txt", "rb+"); //test1.txt的内容是abcdefghijklmnopqrstuvwxyz
if(testFile == NULL)
{
return 0;
}
ret = fread(string, 1, 10, testFile);
//ret = fseek(testFile, 0, SEEK_CUR);
ret = fwrite("hello world", 1, 11, testFile);
fclose(testFile); //test1.txt的内容还是abcdefghijklmnopqrstuvwxyz
//如果fread和fwrite之间调用了fseek,文件内容变成abcdefghijhello worldvwxyz

testFile=fopen("test2.txt", "rb+"); //test2.txt的内容也是abcdefghijklmnopqrstuvwxyz
if(testFile == NULL)
{
return 0;
}
ret = fwrite("hello world", 1, 11, testFile);
//ret = fseek(testFile, 0, SEEK_CUR);
ret = fread(string, 1, 10, testFile);
fclose(testFile); //test2.txt的内容变成hello worldヘヘヘヘヘヘヘヘヘヘvwxyz
//如果fread和fwrite之间调用了fseek,文件内容变成hello worldlmnopqrstuvwxyz

return 0;
}

参考链接:
调用fread后直接调用fwrite写不进去,反之也是,为什么?

《the c programming language》 - 221
FILE *fopen(const char *filename, const char *mode)
fopen opens the named file, and returns a stream, or NULL if the attempt fails. Legal values for mode include:

"r" : open text file for reading
"w" : create text file for writing; discard previous contents if any
"a" : append; open or create text file for writing at end of file
"r+" : open text file for update (i.e., reading and writing)
"w+" : create text file for update, discard previous contents if any
"a+" : append; open or create text file for update, writing at end

Update mode permits reading and writing the same file; fflush or a file-positioning function must be called between a read and a write or vice versa. If the mode includes b after the initial letter, as in "rb" or "w+b", that indicates a binary file. Filenames are limited to FILENAME_MAX characters. At most FOPEN_MAX files may be open at once.

晚风撩人 2017-09-23 1 楼

主要从以下几方面做个比较:

一、 系统调用是由操作系统提供给用户的一种编程接口,用以访问系统的硬件资源和得到某些核心服务,这些对于用户来说是透明的。很显然,他是处于内核及用户之间的。而函数调用处于用户层的,很多函数调用需要通过系统调用实现,比如fseek是基于lseek的。当一个系统调用被执行时,操作系统将进行一系列特殊操作(如保存当前用户状态,进行权限检查等),然后从用户模式切换到内核模式,并根据传入参数和函数调用名取内核寻找对应的核心程序并执行。 从程序完成的功能看,函数库提供的函数通常不需要调用系统服务,函数是在用户空间执行的,除非出现I/O操作,否则一般不会切换到核心态。

二、从执行效率层面看:先看个例子:
假设有一个1MB的文件file.in,分别用系统调用read、write和函数调用fread、fwrite完成逐个字符的读取和复制操作。

in=open("file.in",O_RDONLY);
out=open("fileout",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
while(read(in,&c,1)==1)
{
write(out,&c,1);
}

执行此程序,并使用time工具对此程序运行时间进行测算,大概需要2分半钟,而且几乎消耗了所有的CPU时间。之所以这么慢是因为它必须完成超过两百万次的系统调用,在上下午切换时产生巨大的系统开销。

把上面的read和write分别换成fread和fwrite再测算,执行时间大概为0.2秒.
可以看出似乎函数调用比系统调用执行效率要高,因为函数调用不需要模式切换没有相应的开销。然而事实是,这仅仅对少量数据时才是对的,因为在处理少量数据时,函数调用会把数据先读到缓冲区,等缓冲区满了之后才一次写到目的文件中,这对于进行上百万次的系统调用而每次仅处理1B数据来说当然快多了。而当我们需要处理大量数据,而且每次系统调用会处理整块数据(比如1K甚至1M)时,它就有无可比拟的优势了,因为相对于函数调用,系统调用是直接将数据写入目标文件,而不需要经过缓冲区的。

三、从可移植性层面看:操作系统严格限制用户直接访问某些特殊硬件或软件资源,这时候系统调用便成了用户使用这些资源或服务的唯一途径。如果你编写一个程序,你可能用到C库函数,这些函数实现细节对你来说是透明的,如fread是基于read实现的,直接使用系统调用,移植性差,不能子在win95甚至其他linux系统上运行,而使用POSIX标准写的库函数,在win95上编译后能直接运行,使用C语言写的程序编译时自动与所有的系统调用连接。

四、还有一个不得不提的问题:
当对文件区域加锁后,就必须使用系统调用read或write来访问数据,不能使用fread和fwrite。考虑这样一个例子:

两个程序打算更新同一个文件,假设文件为200个全零的字节组成。程序一先运行,并获得文件头100个字节的写锁,,然后用fread来读取这100字节,它会读取多达BUFSIZE个字节的数据,实际上把整个200字节全读到了缓冲区。接着,程序二开始运行,获得文件后100字节的写锁,并把着100字节写成全1,操作成功,关闭并解锁。这时程序已锁定后100字节,再fread,尽管真正数据现在是全1,但是先前数据已被缓存,所以程序实际上只读到原来的100字节的全0.如果使用read和write就不会有这个问题了。

库函数是高层的,完全运行在用户空间,为程序员提供调用真正的在幕后完成实际事务的系统调用的更方便的接口。系统调用在内核态运行并且由内核自己提供。标准C库函数printf()可以被看做是一个通用的输出语句,但它实际做的是将数据转化为符合格式的字符串并且调用系统调用 write()输出这些字符串。

编译下面的代码我们就可以看到printf()究竟使用了哪些系统调用

#include
int main(void)
{ printf("hello"); return 0; }

使用命令gcc -Wall -o hello hello.c编译。用命令 strace hello 跟踪该可执行文件。我们会惊奇地发现每一行都和一个系统调用相对应。 strace是一个非常有用的程序,它可以告诉你程序使用了哪些系统调用和这些系统调用的参数,返回值。这是一个极有价值的查看程序在干什么的工具。在输出的末尾,你应该看到这样类似的一行 write(1, "hello", 5hello)。这就是我们要找的。藏在面具printf() 的真实面目。