fopen_s如何比fopen更安全?

24
我正在处理针对 Windows 平台的旧代码。在使用 VS2013 编译代码时,出现了以下警告信息:

error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details."

同时,sprintf 也会出现类似的警告。我知道 sprintf_s 比 sprintf 更安全,因为它可以防止缓冲区溢出。
但是,如何理解 fopen_sfopen 更安全呢?因为 fopen 不接受缓冲区,所以不存在缓冲区溢出的情况。请问是否有人能够提供一个案例,证明 fopen 是不安全的,而 fopen_s 是安全的呢?

你链接的文档网站在首页上有这个链接 - user529758
@H2CO3,我已经检查了这个页面,但它没有提到fopen和fopen_s。 - ZijingWu
2个回答

27

s在这种情况下并不代表"安全",而是代表"安全增强"。对于fopen_s,在尝试打开文件之前会检查参数的有效性。

使用fopen,你可以将文件名指针传递为NULL,但很可能会导致一切崩溃。 fopen_s没有这个问题(a)

请记住,像fopen_s这样的边界检查接口是ISO标准的可选部分,在附录K中有详细说明(至少在C11中是这样)。实现不需要提供它们,并且,老实说,如果你是一个懂编程的人,fopen和许多其他所谓的不安全函数其实是非常安全的。

有趣的是,fopen_s会为您捕获NULL指针,但不会捕获无效指针,因此它是安全增强而不是完全安全的-如果您传递了无效但非NULL指针,则仍然可能造成一些损坏。

其他强制要求您提供目标缓冲区大小的“安全”函数只有在传递正确大小时才是安全的。如果传递了太大的大小,一切都无法预料。


(a)来自C11 K.3.5.2.1 fopen_s函数

errno_t fopen_s (
    FILE * restrict * restrict streamptr,
    const char * restrict      filename,
    const char * restrict      mode);

运行时限制

streamptr、filename或mode都不能为null指针。

如果存在运行时限制违规,fopen_s不会尝试打开文件。此外,如果streamptr不是空指针,则fopen_s将*streamptr设置为空指针。

与之相反的是,C11 7.20.5.3 fopen函数 规定了filename和mode必须都是字符串,但没有指定如果提供一个NULL指针会发生什么(大多数实现可能会由于空指针解引用而崩溃)。


当你说“fall to pieces”时,你是指fopen会崩溃,但fopen_s会返回错误吗? - ZijingWu
1
@ZijingWu,是的,这很可能是最常见的情况。fopen的实现可以捕获空文件名或模式并返回错误,但大多数情况下会因为尝试引用错误的内存而进入“la-la land”。已经更新了答案并提供了更多细节。 - paxdiablo
但是,为什么微软要费这么大的劲发明一个新函数并废弃旧函数呢?如果他们可以通过在fopen中添加对空指针的检查来达到相同的效果,而不会对任何人造成伤害和麻烦,那么为什么要这样做呢?如果标准未定义,是什么阻止他们为其实现定义它呢? - Sebastian
2
请注意,标准的 C 库在 C11 §7.1.4 Use of library functions 中指定:以下每个语句都适用,除非在随后的详细描述中明确说明:如果函数的参数具有无效值(例如…或程序地址空间之外的指针、空指针或对应参数未被 const 限定时的不可修改存储器的指针)或…,则行为是未定义的。 因此,将空指针传递给 fopen() 将会引发未定义的行为。 - Jonathan Leffler
请注意,Microsoft C运行时库的Invalid Parameter Handler的默认行为也会故意导致程序崩溃。 - Medinoc

1
当VS2005发布时,我认为这只是微软过于追求“让我们制作专有函数而不是给人们提供snprintf()”的结果,因为两者(默认情况下)都会引发win32异常,如果传递了NULL指针(尽管fopen会引发STATUS_ACCESS_VIOLATION,而fopen_s则会引发STATUS_INVALID_PARAMETER)。这意味着,除非添加Win32特定代码来处理异常,否则两者都会导致程序立即崩溃。
然而,对CRT源代码的一瞥确实揭示了一个小差异:由fopen使用的共享标志是完全宽松的,而由fopen_s使用的共享标志禁止其他进程写入文件。以这种方式,fopen_s更安全,因为它意味着文件不会在您的进程脚下更改。

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