为什么我不能使用fopen?

24

在我之前提出的问题关于所谓的安全库弃用的基础上,我发现自己同样困惑为什么fopen()应该被弃用。

该函数接受两个C字符串,并返回一个FILE*指针,或在失败时返回NULL。哪里存在线程安全问题/字符串溢出问题?还是其他原因?

提前感谢。


请查看:https://dev59.com/r3VD5IYBdhLWcg3wVKAb - David
6
@DavidA,我不认为这是重复的。那个问题想知道如何防止警告。而这个问题想知道为什么它被弃用了。 - paxdiablo
6个回答

46

可以使用fopen()。严肃地说,不要理会微软的建议,他们偏离了ISO标准,给程序员带来了真正的伤害。他们似乎认为编写代码的人都有点傻,不知道在调用库函数之前如何检查参数。

如果有人不愿意学习C编程的复杂性,那么他们就没有做这件事的业务。他们应该转向更安全的编程语言。

这似乎只是微软针对开发人员的另一种供应商锁定的尝试(虽然他们并不是唯一尝试的人,所以我不是在特别抨击他们)。我通常会添加:

#define _CRT_SECURE_NO_WARNINGS

为了确保我在编写完全有效的C代码时不受编译器干扰,我将"-D"变体(或者命令行上的)添加到大多数项目中。

Microsoft在fopen_s()函数中提供了额外的功能(比如文件编码),并更改了返回方式。这可能使它对于Windows程序员更好,但也让代码天生无法移植。

如果你只是编写Windows专用代码,那么可以使用它。但我个人更喜欢能够在任何地方编译和运行代码(尽可能少地进行更改)。


从C11开始,这些安全函数现在已经成为标准的一部分,但是可选的。请查看附录K以了解完整详情。


11
完全同意。使用标准的方法,但要了解所使用方法的局限性。 - rein
5
我非常同意。他们可以尽其所能阻止您调用不安全的函数,但是他们无法阻止您编写自己的例程并超出字符串的结尾。如果您想在C中编程,则必须了解固有的复杂性和不安全性。我认为微软更好地推动人们在适当的情况下使用诸如C#之类的语言,而不是试图改变人们编写C代码的方式。 - Kibbee
4
我认为微软确实将errno定义为一个函数调用,使它成为一个线程本地变量?我目前不在Windows电脑前,因此无法验证。 - Adam Rosenfield
3
好的,他们确实这样做了(至少在 VC++ 2005 SP1 中)。 - Raphaël Saint-Pierre
2
我认为提供更安全的strcmp/strcpy等方法是一个好主意。所以这对于微软来说是有益的。但他们试图使安全方法更加安全(如fopen_s,strncpy_s),这完全是疯狂的,并且忽略了任何标准/可移植性问题。因此,+1表示支持微软,-2表示反对微软,最终得分为-1。 - mmmmmmmm
有一个建议放弃该扩展:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm - Deduplicator

20

有一个官方的ISO/IEC JTC1/SC22/WG14(C语言)技术报告TR24731-1(边界检查接口)及其相关原理可在以下网址中找到:

还有针对TR24731-2(动态分配函数)的工作正在进行中。

fopen_s()的规定理由是:

6.5.2 文件访问函数

当创建文件时,fopen_sfreopen_s函数通过设置文件保护并使用独占访问来提高安全性,以防止未经授权的访问。

规范说明如下:

6.5.2.1 fopen_s函数

概要

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
errno_t fopen_s(FILE * restrict * restrict streamptr,
                const char * restrict filename,
                const char * restrict mode);

运行时约束条件

streamptrfilenamemode中的任何一个都不得为null指针。

如果存在运行时约束条件违规,fopen_s将不尝试打开文件。 此外,如果streamptr不是null指针,则fopen_s*streamptr设置为null指针。

描述

fopen_s函数打开以指向filename的字符串命名的文件,并与之关联一个流。

模式字符串应如fopen所述,但添加了以下以字符"w"或"a"开头的模式:

  • uw 截断为零长度或创建文本文件进行写入,默认权限
  • ua 追加;在文件结尾处打开或创建文本文件以进行写入,默认权限
  • uwb 截断为零长度或创建二进制文件进行写入,默认权限
  • uab 追加;在文件结尾处打开或创建二进制文件以进行写入,默认权限
  • uw+ 截断长度为零或创建文本文件进行更新,默认权限
  • ua+ 追加;打开或创建文本文件以进行更新,在文件结尾处进行写入,默认权限
  • uw+buwb+ 截断为零长度或创建二进制文件进行更新,默认权限
  • ua+buab+ 追加;打开或创建二进制文件以进行更新,在文件结尾处进行写入,默认权限

在底层系统支持这些概念的范围内,打开用于写入的文件应该具有独占(也称为非共享)访问权限。 如果正在创建文件,并且模式字符串的第一个字符不是'u',则在底层系统支持的范围内,文件应具有防止系统上的其他用户访问文件的文件权限。 如果正在创建文件并且模式字符串的第一个字符为“u”,则在关闭文件之前,它应具有系统默认的文件访问权限10)

如果文件成功打开,则通过streamptr指向的FILE指针将设置为控制已打开文件的对象的指针。 否则,通过streamptr指向的FILE指针将设置为null指针。

返回值

fopen_s函数如果成功打开文件,则返回零。 如果它没有打开文件或存在运行时约束条件违规,则fopen_s将返回非零值。

10) 这些权限与通过fopen创建文件时相同。


还有一个放弃它的提议:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm(特别是查看列出采用者的部分)。 - Deduplicator
@Deduplicator:感谢提供链接,非常有趣的阅读。我同意它的建议。 - Jonathan Leffler
1
请参考您是否使用了TR-24731 '安全'函数? — 更新包括对N1967的链接和评论。 - Jonathan Leffler

8
Microsoft在C运行时库中添加了fopen_s()函数,与fopen()相比有以下基本区别:
  • 如果以写入方式打开文件(在模式中指定“w”或“a”),则如果平台支持,文件将以独占(非共享)访问方式打开。
  • 如果在模式参数中使用“u”说明符并带有“w”或“a”说明符,则在关闭文件时,它将具有其他用户访问该文件的系统默认权限(如果系统默认权限为无访问权限,则可能为无访问权限)。
  • 如果在这些情况下未使用“u”指定,则在关闭文件(或之前)时,文件的权限将被设置为其他用户无法访问该文件。

实质上,这意味着应用程序编写的文件默认受到保护,不受其他用户的干扰。

他们没有对fopen()这样做,因为现有代码可能会出现问题。

Microsoft选择弃用fopen(),鼓励Windows开发人员自觉决定其应用程序使用的文件是否具有松散的权限。

Jonathan Leffler的答案提供了fopen_s()的拟议标准化语言。我添加了这个答案,希望能清楚地说明原理。


2

或者是其它什么原因呢?

'fopen' 使用的 FILE 结构的某些实现将文件描述符定义为 'unsigned short'。这意味着你最多只能同时打开 255 个文件,其中还要减去 stdin、stdout 和 stderr。

当然,能够同时打开 255 个文件的价值是有争议的。然而,当你在 Solaris 8 平台上拥有超过 252 个 socket 连接时,这个实现细节会变得非常明显,具体情况可以参考这篇文章(英文)!在我的应用程序中,使用 libcurl 建立 SSL 连接时出现了看似随机的连接失败问题,但最后发现这是由于这个问题引起的。解决这个问题需要部署调试版本的 libcurl 和 openssl,并通过调试器脚本逐步解决问题。

虽然这并不完全是“fopen”的错,但我们可以看到抛弃旧接口的优点;放弃这种古老实现的二进制兼容性可能是选择废弃该接口的原因。


1
你的观点非常有道理,但是新的C标准正在制定中。微软应该将精力集中在改进这一点上(尽管他们可能不会这样做)。 - Dana the Sane
@Dana:这与标准有什么关系?我所看到的唯一限制是FOPEN_MAX必须至少为8。 - Bastien Léonard

1

fopen_s相较于fopen有什么验证功能?如果你参考了http://www.opengroup.org/onlinepubs/009695399/functions/fopen.html,你会发现有一长串的错误代码定义。其他人已经提到errno可以以线程安全的方式实现(或许在MS上也是如此)。 - Matthew Flaschen

1

线程安全。 fopen() 使用全局变量 errno,而 fopen_s() 替代品返回一个 errno_t 并接受一个 FILE** 参数来存储文件指针。


4
我相信 Microsoft C 运行时库会使用一个线程本地变量来存储 errno,因此在这里线程安全不应该成为问题。 - Adam Rosenfield
可以但是在Microsoft CRT中没有实现。 - laalto
2
@laalto: errno不是全局变量,每次使用它时编译器都会调用一个函数来返回errno的地址。因此它很可能是线程安全的。 - Bastien Léonard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接