有没有办法在GCC中使用fopen_s()函数或者至少创建一个关于它的#define?

23

MSVC编译器提示fopen()已过时,建议使用fopen_s()

有没有办法在保持可移植性的前提下使用fopen_s()

是否有#define的解决方案?


在这里有一个类似的问题:https://dev59.com/r3VD5IYBdhLWcg3wVKAb。 - luvieere
6个回答

38

微软的*_s函数是不可移植的,我通常使用等效的C89/C99函数并禁用过时警告(#define _CRT_SECURE_NO_DEPRECATE)。

如果你坚持要使用,可以使用一个适配器函数(不一定是宏!),将fopen()委托给没有fopen_s()的平台,但你必须小心地将errno_t返回代码的值与errno进行映射。

errno_t fopen_s(FILE **f, const char *name, const char *mode) {
    errno_t ret = 0;
    assert(f);
    *f = fopen(name, mode);
    /* Can't be sure about 1-to-1 mapping of errno and MS' errno_t */
    if (!*f)
        ret = errno;
    return ret;
}

然而,我并不认为fopen_s()fopen()更加安全,因此通常我会选择可移植性。


9
有趣的是,它们现在成为了C11的一部分(虽然是作为可选的附录K)。 - rubenvb
比我以前的纯宏方法好多了(现在已经不需要了)。你的函数方法通过返回errno(=EINVAL,即22,fwiw)部分地复制了fopen_s失败时的行为。你还可以生成一个“无效参数异常”来更加精确地匹配fopen_s的行为。 - riderBill
2
顺便提一下,根据这里的说明,errno“扩展为int类型的静态可修改lvalue(在C++11之前)”,并且“扩展为int类型的线程本地可修改lvalue(自C++11起)”。因此,您的返回类型应该是int。 - riderBill
2
有趣的是,它们现在成为了C11的一部分。微软的版本不是Annex K的一部分:“Microsoft Visual Studio实现了API的早期版本。然而,该实现是不完整的,既不符合C11也不符合原始的TR 24731-1。由于与规范的许多偏差,Microsoft的实现不能被认为是符合或可移植的。” - Andrew Henle

10

在C/C++代码中,

#ifdef __unix
#define fopen_s(pFile,filename,mode) ((*(pFile))=fopen((filename),(mode)))==NULL
#endif

在 Makefile 中。
CFLAGS += -D'fopen_s(pFile,filename,mode)=((*(pFile))=fopen((filename),(mode)))==NULL'

注意,成功打开文件时,fopen_s返回0,而fopen返回一个非零的文件指针。因此,在宏的末尾需要添加“==NULL”,例如:

if (fopen_s(&pFile,filename,"r")) perror("cannot open file");

2
你错过了fopen_s()fopen()失败时返回errno的部分。 - Toby Speight

8
如果您正在使用C11,fopen_s是一个标准库。在gcc中,你需要使用--std=c11参数。

5
这个答案是错误的。根据C11标准的附录Kfopen_s()和其他函数是可选的,根据K.2范围第1段:“本附录指定了一系列可选扩展……”实际上,截至2019年,只有微软实现了Annex K,而微软的实现被认为“不能被视为符合或可移植”的(http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm)。 - Andrew Henle
1
@AndrewHenle 即使你可能是对的,问题直接要求GCC使用fopen_s。因此,它可能不是通用解决方案,但它适合问题,说它是错误的太过严格,可能不完整。根据C语言标准,既不是C ANSI也不是C99,在某些复杂软件中可能不是100%可移植的,因为它太低级了。所以又回到了权衡。 - Raffaello
2
@Raffaello GCC从未实现过Annex K,也永远不会。一些原因在此:"让我们反对这个提案。它是有争议的,可能导致软件更容易出错,并且不反映社区的共识。" - Andrew Henle
1
还有这个链接:http://sourceware-org.1504.n7.nabble.com/Implement-C11-annex-K-td279910.html。我的看法是,附录K是一个巨大的错误——委员会最新的迭代版本被一个“赞助商”的花招所迷惑,该赞助商对实际实现标准没有兴趣,并且在过去20多年里一直试图破坏C语言。 - Andrew Henle
1
此外,还可以参考https://dev59.com/E1UL5IYBdhLWcg3wK1gU等许多其他问题和答案,这些问题和答案指出了Annex K及其*_s“安全”函数的问题。简而言之,它们是不可移植的,并且在任何实际使用中唯一的实现 - Microsoft的实现也是不符合标准的。 - Andrew Henle
显示剩余3条评论

3
许多微软的安全功能包含在C11标准的附录K中,但其支持度不高,因此可移植性仍然存在问题。某些应用程序需要提高安全性,也许未来会有所改善。
过去,我是这样做的:
  #define fopen_s(fp, fmt, mode)          *(fp)=fopen( (fmt), (mode))

宏很简单直接,适用于快速且不太正规的任务,但它不提供fopen_s的异常行为,并且它不会提供真正的fopen_s函数的安全性。
@Alex B上述的函数方法在失败时部分地复制了正确的行为;他返回errno(=EINVAL)。他的方法可以进一步扩展,通过生成“无效参数异常”来更完整地复制fopen_s的行为。

-1
#define fopen_s(fp, fmt, mode)  ({\
    *(fp)=fopen( (fmt), (mode));\
    (*(fp) ) ? 0:errno;\
})

4
感谢您提供这个代码片段,它可能会在短期内提供一些有限的帮助。通过展示为什么这是一个好的解决方案,适当的解释将大大提高其长期价值,并使其对未来读者具有其他类似问题的更多用处。请编辑您的答案以添加一些解释,包括您所做出的假设。 - Toby Speight
我刚刚添加了"({...})",这是GCC和Clang支持的,以返回基于@riderBill第二层答案的值。感谢riderBill。 - Tim Wong

-2
根据https://en.cppreference.com/w/c/io/fopen,在标准库中可以启用*_s函数:

与所有边界检查函数一样,只有在实现定义了__STDC_LIB_EXT1__并且用户在包含之前将__STDC_WANT_LIB_EXT1__定义为整数常量1时,才保证可以使用。


1
正如引用的文本所说,只有在实现定义了__STDC_LIB_EXT1__时才可以使用此选项。大多数实现都没有定义它。 - Toby Speight

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