我一直觉得 C 函数 fopen()
接受一个 const char *
作为第二个参数是很奇怪的。我认为,如果在 stdio.h
中定义了位掩码,例如 IO_READ
等,那么阅读代码和实现库都会更容易,这样你就可以执行以下操作:
FILE *myFile = fopen("file.txt", IO_READ | IO_WRITE);
这样做的编程原因是什么,还是只是历史原因?(即“那就是它现在的样子”)
我一直觉得 C 函数 fopen()
接受一个 const char *
作为第二个参数是很奇怪的。我认为,如果在 stdio.h
中定义了位掩码,例如 IO_READ
等,那么阅读代码和实现库都会更容易,这样你就可以执行以下操作:
FILE *myFile = fopen("file.txt", IO_READ | IO_WRITE);
我相信,与简单的位掩码相比,使用字符串的优点之一是它允许平台特定的扩展,这些扩展不是位设置。纯粹假设:
FILE *fp = fopen("/dev/something-weird", "r+,bs=4096");
open()
调用需要告诉块大小,而不同的调用可以使用完全不同的大小等。尽管 I/O 现在已经被很好地组织起来(原来并非如此——设备多种多样,访问机制远非统一),因此似乎很少需要。但是字符串值的打开模式参数更好地允许了这种可扩展性。fopen()
函数确实需要沿着这里描述的一般线路带有额外的参数——正如 Andrew Henle 所指出的(谢谢!)。手册中包括了这个示例调用(稍作重新格式化):FILE *fp = fopen("myfile2.dat", "rb+, lrecl=80, blksize=240, recfm=fb, type=record");
底层的open()
函数必须通过ioctl()
(I/O控制)调用或fcntl()
(文件控制)函数进行增强,或者使用隐藏它们的函数以达到类似的效果。
fopen
可以是一个可变参数函数,接受二进制标志和可选的实现定义的额外参数。 - user16217248fopen
可以是一个可变参数函数... 但目前的 API 不支持。与 open()
不同,O_CREAT
被设置为指示 mode
参数存在的标志,而无法使用传递给 fopen()
的字符串来指示任何类型的扩展参数的存在或不存在,除非将这些字符串的内容扩展到现有标准值之外,并且 这将更改每个人的函数原型,对已编译的代码可能产生未知的后果。如果你要这样做... - Andrew Henle丹尼斯·里奇(1993年)撰写了一篇C语言历史的文章,介绍了C语言是如何逐渐从B语言演变而来的。一些设计决策是为了避免对已经用B语言或C语言早期版本编写的现有代码进行源代码更改。
特别地,莱斯克编写了一个“可移植I/O包”[Lesk 72],后来被重新制作成C标准I/O例程。
C预处理器直到1972/3才被引入,因此莱斯克的I/O包是在没有它的情况下编写的!(在非常早期的还不是C语言的版本中,在使用的平台上指针适合于整数,并且将一个隐式int返回值分配给指针是完全正常的。)
许多其他变化发生在1972-3年左右,但最重要的是预处理器的引入,部分原因是由于艾伦·斯奈德的建议[Snyder 74]。
没有#include
和#define
,像IO_READ | IO_WRITE
这样的表达式就不是一个选项。
如果没有CPP,1972年使用fopen
时的选项可能在典型源代码中如下:
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
open(2)
)因缺乏预处理器而被排除在外。fopen
来说,这已经足够了(也更有效率):它们只支持单个字符字符串,检查*mode
是否为r
、w
或a
。(请参见@Keith Thompson's answer。)显然,读写(不截断)的r+
是后来出现的。(请参见fopen(3)
以获取现代版本。)
C语言确实有一个字符数据类型(作为产生胚芽C的第一步之一于1971年添加到B中,因此在1972年仍然很新。原始的B没有char
,因为它是为将多个字符打包到一个字中的机器编写的,所以char()
是一个索引字符串的函数!请参见Ritchie的历史文章。)
使用单字节字符串相当于通过const-reference传递char
,因为库函数无法内联,会带来额外的内存访问开销。(即使是简单的函数(例如fopen)在同一编译单元中也不会被内联,原始编译器可能根本不会内联任何东西;现代风格的小助手函数依赖于现代编译器将它们内联。)
PS:史蒂夫·杰索普(Steve Jessop)使用同样的引用激励了我写这篇文章。
可能相关:strcpy() return value。 strcpy
可能也是早期编写的。
fseek
可以使用整数常量(SEEK_SET
, SEEK_CUR
, SEEK_END
),但fopen
不能呢? - user16217248fopen
接口,以免为时已晚(现在有点晚了)。 - user16217248一个词:遗留。不幸的是,我们必须与之共存。
只是猜测:也许在当时,const char *
看起来更灵活,因为它没有任何限制。位掩码只能有32个不同的值。现在看来对我来说似乎是 YAGNI。
更多猜测:家伙们很懒,写 "rb"
比写 MASK_THIS | MASK_THAT
更省打字 :)
fopen
设计的时代。 - GManNickGopen()
系统调用只有两个参数。如果open()
因文件不存在而失败,则必须使用名称和模式调用creat()
来创建文件。 - Jonathan Leffler我必须说,我很感激这一点 - 我知道要输入“r”,而不是IO_OPEN_FLAG_R、IOFLAG_R、SYSFLAGS_OPEN_RMODE或其他什么。
fopen()
接口的人)只是喜欢使用字符串来指定模式,而不是位图。
2. 他们可能希望接口与Unix系统调用接口类似,但又不会误将常量定义为Unix而不是C库。fopen()
采用了位图模式参数,并使用标识符OPENMODE_READONLY
来指定今天由模式字符串"r"指定的文件。现在,如果有人在Unix平台上编译程序并进行以下调用(并且已经包含了定义O_RDONLY
的头文件):fopen( "myfile", O_RDONLY);
虽然没有编译器错误,但是除非OPENMODE_READONLY
和O_RDONLY
被定义为相同的位,否则您将得到意外的行为。当然,C标准名称与Unix名称定义为相同的名称是有意义的,但也许他们想要避免需要这种耦合。
再说,也许他们根本没有考虑过这个问题...
我找到的关于fopen
的最早参考资料是在Kernighan和Ritchie的第一版《C程序设计语言》(K&R1)中,出版于1978年。
书中展示了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
,函数将打印错误消息并终止您的程序,而不是返回空指针(我猜测实际的库版本没有这样做)。该书强调简单的代码而非错误检查。mode
参数,但看起来它被定义为字符串只是为了方便。单个字符也可以工作,但字符串至少使未来的扩展成为可能(这是该书没有提到的)。strlen
被发明时,const
也不存在,但我认为我们不能从中得出结论,即 strlen
最初可能接受除字符串指针之外的任何参数;-) 只是说“字符串”的典型方式发生了变化。 - Steve Jessopint
的参数无法做到这一点。 C99 Rationale V5-10 7.19.5.3 The fopen
function 指出,例如:
Other specifications for files, such as record length and block size, are not specified in the Standard due to their widely varying characteristics in different operating environments.
Changes to file access modes and buffer sizes may be specified using the setvbuf function (see §7.19.5.6).
An implementation may choose to allow additional file specifications as part of the mode string argument. For instance,
file1 = fopen(file1name, "wb,reclen=80");
might be a reasonable extension on a system that provides record-oriented binary files and allows a programmer to specify record length.
类似的文本在C89 Rationale 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
的参数中!!正如Tuomas Pelkonen所说,这是遗留问题。
就我个人而言,我想知道是否有些误入歧途的人认为由于输入字符较少而更好?在早期,程序员的时间比今天更有价值,因为它不太可访问,编译器也不是很好等等。
这只是猜测,但我可以理解为什么有些人会喜欢在这里和那里节省一些字符(请注意标准库函数名称中的简洁性...我认为string.h的“strstr”和“strchr”是不必要的简洁性的最佳例子)。
fprintf
和sprintf
之所以要区分,是因为它们在前6个字符中必须不同。如果第6个字符后的字符被省略,那么允许符号比6个字符更长,但必须是不同的。因此,我想只要以6个可能的无意义字符开头,它们就可以更易读。 - Michael Burr