C语言 为什么fopen把字符串作为它的第二个参数?

7ajki6be  于 2023-04-05  发布在  其他
关注(0)|答案(9)|浏览(132)

我一直觉得奇怪的是,C函数fopen()const char *作为第二个参数。我认为如果在stdio.h中定义了位掩码,比如IO_READ等,那么阅读代码和实现库都会更容易,所以你可以做这样的事情:

FILE *myFile = fopen("file.txt", IO_READ | IO_WRITE);

它实际上是有一个程序性的原因,还是它只是历史性的?(即 *“这就是它的方式。

3bygqnnd

3bygqnnd1#

我相信字符串代替简单的位掩码的优点之一是它允许平台特定的扩展,而不是位设置。纯粹假设:

FILE *fp = fopen("/dev/something-weird", "r+,bs=4096");

对于这个小发明,open()调用需要被告知块的大小,不同的调用可以使用完全不同的大小,等等。诚然,I/O现在已经组织得很好了(最初不是这样的-设备非常多样化,访问机制远远没有统一),所以它似乎很少是必要的。但是字符串值开放模式参数更好地考虑了这种扩展性。
在IBM的大型机MVS o/s上,fopen()函数确实沿着这里所描述的一般线路接受额外的参数-正如Andrew Henle所指出的那样(谢谢!)。手册页面包括示例调用(稍微重新格式化):

FILE *fp = fopen("myfile2.dat", "rb+, lrecl=80, blksize=240, recfm=fb, type=record");

底层的open()必须通过ioctl()(I/O控制)调用或fcntl()(文件控制)或隐藏它们的函数来增强,以实现类似的效果。

6mw9ycah

6mw9ycah2#

Dennis里奇(1993年)编写了an article about the history of C,以及它是如何从B逐渐演变而来的。一些设计决策的动机是避免对用B或C的早期版本编写的现有代码进行源代码更改。
特别是,Lesk写了一个“可移植I/O包”[Lesk 72],后来被修改成C“标准I/O”例程

C预处理器直到1972/3年才被引入,所以Lesk的I/O包没有它!(在早期的非C语言中,指针适合使用平台上的整数,并且将隐式int返回值分配给指针是完全正常的。

1972- 1973年左右发生了许多其他变化,但最重要的是预处理器的引入,部分是在Alan Snyder的敦促下[Snyder 74]
如果没有#include#define,像IO_READ | IO_WRITE这样的表达式就不是一个选项。
在1972年,fopen调用在没有CPP的典型源代码中可以看到的选项是:

FILE *fp = fopen("file.txt", 1);       // magic constant integer literals
FILE *fp = fopen("file.txt", 'r');     // character literals
FILE *fp = fopen("file.txt", "r");     // string literals

魔术整数字面量显然是可怕的,所以不幸的是,显然最有效的选择(Unix后来为open(2)采用)因缺乏预处理器而被排除在外。
字符文字显然是不可扩展的;这对API设计者来说可能是显而易见的,但对于fopen的早期实现来说已经足够了(并且更有效):他们只支持单字符串,检查*mode是否是rwa。(参见@基思Thompson的回答。)显然,r+用于读+写(没有截断)是后来才出现的。(参见fopen(3)的现代版本。)
C确实有一个字符数据类型(1971年添加到B中,作为产生胚胎C的第一步之一,所以它在1972年仍然是新的。原始B没有char,因为它是为将多个字符打包成一个单词的机器编写的,所以char()是一个索引字符串的函数!参见里奇的历史文章。
使用单字节字符串实际上是通过const-reference传递char,因为库函数不能内联,所以内存访问的所有额外开销。(原始编译器可能不会内联任何东西,即使是在同一编译单元中的琐碎函数(不像fopen),它会缩小总代码大小以内联它们;现代风格的微型助手函数依赖于现代编译器来内联它们。
PS:史蒂夫·杰索普的回答同样的报价启发了我写这篇文章。
可能相关:strcpy() return value.strcpy可能也写得很早。

gmxoilav

gmxoilav3#

一个词:遗产。不幸的是,我们不得不忍受它。
只是推测:也许当时const char *似乎更灵活的解决方案,因为它没有任何限制。位掩码只能有32个不同的值。现在看起来像YAGNI
更多猜测:伙计们都很懒,写"rb"比写MASK_THIS | MASK_THAT需要更少的输入:)

qncylg1j

qncylg1j4#

我必须说我很感激它-我知道键入“r”而不是IO_OPEN_FLAG_R,或者是IOFLAG_R或SYSFLAGS_OPEN_RMODE或其他什么

oymdgrw7

oymdgrw75#

我推测它是以下一个或多个(不幸的是,我无法快速找到任何类型的支持参考,所以这可能仍然是猜测):

  1. Kernighan或里奇(或任何提出fopen()接口的人)恰好喜欢使用字符串而不是位图指定模式的想法
    1.他们可能希望该接口与Unix open()系统调用接口类似但又有明显的不同,这样它就可以立即熟悉,但不会错误地使用为Unix定义的常量而不是C库定义的常量进行编译
    例如,假设虚构的C标准fopen()采用了一个位图模式参数,使用标识符OPENMODE_READONLY来指定文件what today由模式字符串“r”指定。现在,如果有人对在Unix平台上编译的程序进行了以下调用(并且包含了定义O_RDONLY的头部):
fopen( "myfile", O_RDONLY);

这样就不会有编译错误,但是除非OPENMODE_READONLYO_RDONLY被定义为相同的位,否则你会得到意想不到的行为。当然,C标准的名字与Unix的名字定义相同是有意义的,但是也许他们想排除这种耦合的要求。
不过,他们可能根本就没想到……

zkure5ic

zkure5ic6#

我发现的最早的fopen参考文献是Kernighan &里奇在1978年出版的第一版《C编程语言》(K&R1)。
它展示了fopen的示例实现,这可能是当时C标准库实现中代码的简化版本。下面是书中代码的简化版本:

FILE *fopen(name, mode)
register char *name, *mode;
{
    /* ... */
    if (*mode != 'r' && *mode != 'w' && *mode != 'a') {
        fprintf(stderr, "illegal mode %s opening %s\n",
            mode, name);
        exit(1);
    }
    /* ... */
}

查看代码,mode应该是一个1个字符的字符串(无"rb",文本和二进制之间没有区别)。如果传递的字符串较长,则忽略第一个字符之后的任何字符。如果传递的字符串无效,则忽略第二个字符。该函数将打印错误消息并终止程序,而不是返回空指针(我猜实际的库版本没有做到这一点)。这本书强调简单的代码而不是错误检查。
很难确定,特别是考虑到这本书没有花很多时间解释mode参数,但它似乎只是为了方便而被定义为字符串。单个字符也可以工作,但字符串至少使未来的扩展成为可能(这本书没有提到)。

ql3eal8s

ql3eal8s7#

Dennis里奇在http://cm.bell-labs.com/cm/cs/who/dmr/chist.html上说
特别是,Lesk写了一个“可移植I/O包”[Lesk 72],后来被修改成C“标准I/O”例程
所以我说问Mike Lesk,把结果贴在这里作为你自己问题的答案,并因此获得一堆积分。- )

sqserrrh

sqserrrh8#

原因很简单:一个类型为int的参数不能做到这一点。C99 Rationale V5-10 7.19.5.3 The fopen function说,例如
文件的其他规格,如记录长度和块大小,由于它们在不同的操作环境中有很大的差异,因此在标准中没有规定。
可以使用setvbuf函数指定文件访问模式和缓冲区大小的更改(参见§7.19.5.6)。
实现可以选择允许附加文件规范作为模式字符串参数的一部分。

file1 = fopen(file1name, "wb,reclen=80");

对于提供面向记录的二进制文件并允许程序员指定记录长度的系统,可能是一个合理的扩展。
C89原理www.example.com中有类似的文本4.9.5.3
当然,如果使用|艾德枚举标志,那么这些类型的扩展将是不可能的。
使用这些参数的fopen实现的一个例子是在z/OS上。下面是一个例子:

/* The following call opens:                                                 
              the file myfile2.dat,                                             
              a binary file for reading and writing,                            
              whose record length is 80 bytes,                                  
              and maximum length of a physical block is 240 bytes,              
              fixed-length, blocked record format                               
              for sequential record I/O.                                        
   */                                                                           

   if ( (stream = fopen("myfile2.dat", "rb+, lrecl=80,\                         
      blksize=240, recfm=fb, type=record")) == NULL )                           
      printf("Could not open data file for read update\n");

现在,想象一下,如果您必须将所有这些信息压缩到一个类型为 * int * 的参数中!!

lnvxswe2

lnvxswe29#

正如Tuomas Pelkonen所说,这是遗产。
就我个人而言,我想知道是否有些被误导的傻瓜认为它更好,因为输入的字符更少?在奥登,程序员的时间比今天更有价值,因为它不太容易访问,编译器也没有那么好。
这只是推测,但我可以理解为什么有些人会喜欢在这里或那里保存一些字符(注意,任何标准库函数名称都缺乏冗长......我将string.h的“strstr”和“strchr”作为可能是不必要的简洁的最佳示例)。

相关问题