为什么C的“fopen”将“const char *”作为第二个参数?

时间:2010-03-25 20:50:25

标签: c file-io history

C函数“fopen”将“const char *”作为第二个参数,这总是让我感到奇怪。如果在stdio.h中定义了位掩码,比如“IO_READ”等,我认为读取代码和实现库代码会更容易,所以你可以这样做:

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

它的实际存在方式是否存在程序化原因,还是仅仅是历史性的? (即“那就是它的方式。”)

9 个答案:

答案 0 :(得分:6)

一个词:遗产。不幸的是,我们必须忍受它。

只是推测:也许在当时“const char *”似乎更灵活的解决方案,因为它不受任何限制。位掩码只能有32个不同的值。现在看起来像YAGNI。

更多猜测:Dudes很懒,写“rb”比MASK_THIS |需要更少的输入MASK_THAT:)

答案 1 :(得分:4)

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

  1. Kernighan或Ritchie(或任何提出fopen()接口的人)碰巧喜欢使用字符串而不是位图指定模式的想法
  2. 他们可能希望接口与Unix open()系统调用接口有相似之处,因此它会立即熟悉但不会错误地使用为Unix而不是C库定义的常量进行编译
  3. 例如,假设采用位图模式参数的神秘C标准fopen()使用标识符OPENMODE_READONLY来指定今天由模式字符串“r”指定的文件。现在,如果有人对在Unix平台上编译的程序进行了以下调用(并且已经包含了定义O_RDONLY的头文件):

    fopen( "myfile", O_RDONLY);
    

    没有编译器错误,但除非OPENMODE_READONLYO_RDONLY定义为相同的位,否则会出现意外行为。当然,将C标准名称定义为与Unix名称相同是有意义的,但也许他们想要排除需要这种耦合。

    然后,这可能根本没有超出他们的想法......

答案 2 :(得分:4)

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

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

对于这个Gizmo,open()调用需要告诉块大小,不同的调用可以使用完全不同的大小等等。当然,I / O现在已经很好地组织了(事实并非如此)最初 - 设备非常多样化,访问机制远非统一),因此很少有必要。但是字符串值的开放模式参数允许更好的可扩展性。

在IBM的大型机MVS o / s上,fopen()函数确实在此处描述的一般行中采用了额外的参数 - noted Andrew Henle(谢谢!)。手册页包含示例调用(稍微重新格式化):

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

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

答案 3 :(得分:4)

我发现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参数,但看起来它只是为了方便而定义为字符串。单个角色也可以起作用,但是至少一个字符串可以使未来扩展成为可能(本书没有提及)。

答案 4 :(得分:3)

Dennis Ritchie从http://cm.bell-labs.com/cm/cs/who/dmr/chist.html

那里说
  

特别是莱斯克写的“便携式”   I / O包'[Lesk 72]后来   重新设计成为C`标准   I / O'例程

所以我说问Mike Lesk,在这里发布结果作为你自己问题的答案,然后为它赚取一堆积分。虽然你可能想让这个问题听起来不那么像批评; - )

答案 5 :(得分:2)

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

答案 6 :(得分:2)

Dennis Ritchie(1993年)写了an article about the history of C,以及它是如何从B逐步演变而来的。某些设计决策是通过避免对用B编写的现有代码或C的原始版本进行源代码更改而产生的。

  

尤其是,莱斯克(Lesk)   后来的“ I / O软件包” [第72课]   重做成为C`标准   I / O例程

C预处理器直到1972/3才被引入,因此,Lesk的I / O软件包是在没有它的情况下编写的!(在非常早的尚未使用C的平台上,指针适合整数)被使用,并且将隐式int返回值分配给指针是完全正常的。)

  

1972-3年左右发生了许多其他变化,但最重要的是预处理器的引入,部分是在Alan Snyder的敦促下[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

不可思议的整数常量显然很可怕,因此不幸的是,由于缺少预处理器,显然最有效的选项(Unix在open(2)之后采用了该选项)。

一个字符文字显然是不可扩展的;大概即使在那时,这对于API设计人员来说也是显而易见的。但这对于fopen的早期实现就足够了(并且效率更高):他们仅支持单字符字符串,检查*moderw还是a。 (请参阅@Keith Thompson's answer。)显然,稍后进行了r+的读写操作(无截断)。 (有关最新版本,请参见fopen(3)。)

C确实有字符数据类型(作为生产胚胎C的第一步,1971年被添加到B中,因此它在1972年仍然是新的。原始B没有{{1 }}是为将多个字符包装到一个单词中的机器编写的,因此char是一个索引字符串的函数!请参阅Ritchie的历史文章。)

使用单字节字符串可有效地通过const引用传递char(),并具有所有额外的内存访问开销,因为库函数无法内联。 (而且原始编译器可能没有内联任何东西,即使是同一个编译单元中的琐碎函数(与fopen不同)也不会内联,它们会缩小总代码大小以内联它们;现代风格的小助手函数依赖于现代编译器来内联它们。)


PS:史蒂夫·杰索普(Steve Jessop)的回答带有相同的引用,这启发了我写这篇文章。

可能与之相关:strcpy() return valuechar也可能写得很早。

答案 7 :(得分:2)

原因很简单:允许C实现根据需要扩展模式。类型为int的自变量不会做到这一点,例如C99 Rationale V5-10 7.19.5.3 The fopen function说。那

  

由于在不同的操作环境中文件的特性差异很大,因此未在文件中指定其他文件规范,例如记录长度和块大小。

     

可以使用setvbuf函数指定对文件访问模式和缓冲区大小的更改   (请参阅第7.19.5.6节)。

     

实现可以选择允许其他文件规范作为方式字符串参数的一部分。例如,

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

在提供面向记录的二进制文件并允许以下操作的系统上可能是合理的扩展:   程序员指定记录长度。

C89 Rationale 4.9.5.3

中存在相似的文本

自然地,如果使用| ed枚举标志,则将无法进行此类扩展。

z/OS上有一个使用这些参数的fopen实现的示例。一个示例包含以下摘录:

   /* 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 类型的一个参数!

答案 8 :(得分:0)

正如Tuomas Pelkonen所说,这是遗产。

就个人而言,我想知道是否有一些被误导的小卒认为由于输入的字符较少而使其更好?在过去的日子里,程序员的时间比今天更有价值,因为它不太容易访问,编译器也不那么好。

这只是猜测,但我可以看到为什么有些人会赞成在这里和那里保存一些字符(注意任何标准库函数名称都没有冗长...我提出了string.h的“strstr”和“strchr”可能是不必要简洁的最佳例子。