关于MSVC编译器,open()、_open()和fopen()的区别是什么?

4
我看到这三个函数都与打开文件有关。 open

这个 POSIX 函数已被弃用。请使用符合 ISO C++ 标准的 _open 替代。

_open

打开一个文件。这些函数已被弃用,因为更安全的版本可用;请参阅 _sopen_s、_wsopen_s。

fopen

打开一个文件。可用于执行额外参数验证并返回错误代码的更安全版本的这些函数;请参阅 fopen_s、_wfopen_s。

所以,为什么有三个版本?在什么情况下使用哪个版本?我认为 POSIX 很好,但为什么 MSDN 说 open 的 POSIX 版本已经被弃用了呢?还有,是否有任何与前导下划线相关的命名约定,以便我可以根据函数名称的首字母选择正确的函数?
当我查看 ACPICA 代码 时,我看到以下代码:似乎 _XXX 版本可以禁用一些 MS 语言扩展,这些扩展具体是什么?
/*
 * Map low I/O functions for MS. This allows us to disable MS language
 * extensions for maximum portability.
 */
#define open            _open
#define read            _read
#define write           _write
#define close           _close
#define stat            _stat
#define fstat           _fstat
#define mkdir           _mkdir
#define snprintf        _snprintf
#if _MSC_VER <= 1200 /* Versions below VC++ 6 */
#define vsnprintf       _vsnprintf
#endif
#define O_RDONLY        _O_RDONLY
#define O_BINARY        _O_BINARY
#define O_CREAT         _O_CREAT
#define O_WRONLY        _O_WRONLY
#define O_TRUNC         _O_TRUNC
#define S_IREAD         _S_IREAD
#define S_IWRITE        _S_IWRITE
#define S_IFDIR         _S_IFDIR

ADD 1

看起来单下划线前缀_XXX是微软的惯例。例如_DEBUG_CrtSetDbgFlag和上述的_open。一些引用自MSDN

在Microsoft C++中,以两个下划线开头的标识符被保留给编译器实现。因此,Microsoft的惯例是在Microsoft特定的关键字前加上双下划线。这些单词不能用作标识符名称。
默认情况下启用Microsoft扩展功能。为确保您的程序完全可移植,您可以在编译时通过指定ANSI兼容的/Za命令行选项(编译为ANSI兼容性)来禁用Microsoft扩展功能。这样做时,Microsoft特定的关键字将被禁用。
当启用Microsoft扩展功能时,您可以在程序中使用Microsoft特定的关键字。为了符合ANSI标准,这些关键字以双下划线为前缀。为了向后兼容,除了__except、__finally、__leave和__try之外,所有双下划线关键字的单下划线版本都得到支持。此外,__cdecl可用且没有前导下划线。
__asm关键字替换了C++ asm语法。asm被保留以与其他C++实现兼容,但未实现。请使用__asm。
__based关键字在32位和64位目标编译中的使用有限。
尽管根据上述引用,__int64_int64都应该可以使用,但Visual Studio没有为_int64提供语法高亮。但是_int64也可以编译。

ADD 2

snprintf() 和 _snprintf()


你正在使用C++编译文件。你必须告诉VC将其编译为C。在POSIX中,open()并未被放弃使用。 - Stargateur
1
似乎Windows就是Windows,他们不希望人们使用POSIX的open()函数。因此可以说MSVC不符合POSIX标准。 - Stargateur
1
1)这不是关于C语言,而是有关附加的库函数。 2)他们在这一方面是正确的,因为open可能会与用户名称冲突。而_open使用了实现保留的名称。 3)open是POSIX标准,所以如果你编写POSIX程序,就使用它。没有人使用微软的扩展_open等。 4)微软对于C和POSIX标准来说是一个非常糟糕的参考。众所周知,他们有意违反这些标准(例如,MSVC自18年前起故意不符合标准(C99)。事实上,我记得他们甚至不能保证完全遵循C90。 5)如果你使用C进行编程,请使用像gcc或clang这样符合标准的编译器。 - too honest for this site
1个回答

6
  • 就Windows而言,打开文件的函数是CreateFile。它返回一个HANDLE,由Kernel32.dll提供,而不是由Visual Studio提供。这个HANDLE可以传递给其他Windows API函数。

  • _openopen函数是POSIX兼容性函数,用于帮助您在Windows上编译为POSIX(Linux、macOS、BSD、Solaris等)编写的程序。这些函数由Visual Studio的C运行时定义,并且可能在内部调用CreateFile。函数的POSIX名称是open,但此处定义的函数为_open,以防您已经在代码中定义了一个名为open的函数。该函数返回一个int,可传递给其他POSIX函数。在Windows上,此接口是由Visual Studio提供的兼容性API,但在Linux和macOS上,此接口是操作系统的直接接口,就像Windows上的HANDLE一样。

  • fopen函数是C标准的一部分。它由Visual Studio的C运行时定义,并且可能在内部调用CreateFile。它返回一个FILE *,可以传递给C标准定义的其他函数。

因此,总结这些选项:

  • 如果您需要直接使用Windows API,例如调用GetFileInformationByHandleCreateFileMapping,则需要一个HANDLE,并且您应该调用CreateFile来打开文件。

  • 如果您有一个已经针对POSIX系统编写的程序,则可以使用open使其更容易移植到Windows。如果您只是为Windows编写,则使用此接口没有任何优势。

  • 如果您的程序只需要进行基本的文件操作,如打开,读取和写入,则fopen就足够了,并且它也可以在其他系统上工作。 FILE *可以(通常)由您的应用程序缓冲,并支持方便的操作,如fprintffscanffgets。如果您想在通过CreateFileopen返回的文件上调用fgets,则必须自己编写。

可以将文件句柄从一种 API 转换为另一种 API,但要注意所有权问题。 "所有权" 实际上并不是一个技术概念,它只是描述了谁负责管理对象状态,您希望避免销毁您不拥有的对象,并避免为同一对象拥有多个所有者。

  • 对于 Windows API,您可以使用 _open_osfhandle()HANDLE 创建为 FILE *,并使用 _get_osfhandle()FILE * 获取 HANDLE。 但是,在这两种情况下,句柄都将由 FILE * 拥有。

  • 对于 POSIX API,您可以使用 fdopen()int 文件描述符创建为 FILE *,并使用 fileno()FILE * 获取 int 文件描述符。同样,在这两种情况下,文件都将由 FILE * 拥有。

请注意,可移植性的复杂性在于Windows文件名是数组,而macOS / Linux /等的文件名是数组。
如果您使用不同的C运行时,如MinGW,或者使用Windows子系统来运行Linux,则情况将有所不同。

注意:可以使用fdopen()函数,因此您可以同时使用open()fgets()函数(在Linux上可能可以创建套接字并将其转换为FILE)。 - Stargateur
很遗憾,这个问题涉及到Windows,而在Windows上无法使用fdopen()函数打开一个套接字。 - Dietrich Epp
抱歉,我不清楚这只是 fdopen() 的用例。因为我没有其他示例,您说:“如果要在由 CreateFile 或 open 返回的文件上调用 fgets,则必须自己编写它。”但这是错误的,您可以使用 fdopen()open()。或者也许 Windows 没有实现这个? - Stargateur
@Stargateur:是的,这是真的,你可以使用fdopen()从一个int文件描述符获取一个FILE *,同样地,你也可以使用_open_osfhandle()从一个文件的HANDLE获取一个FILE *。在这里需要记住的重要部分是,这些是用于处理文件的三个不同API。HANDLE是与Windows本身通信的API的一部分。FILE *是使用标准C库中的代码的API的一部分,这个库可能是一个带有缓冲区的HANDLE的包装器。int是POSIX API的一部分,在Windows上是一个兼容层,没有缓冲。 - Dietrich Epp
@Stargateur:如果您想使用fdopen()fileno()_open_osfhandle()_get_osfhandle(),则必须了解FILE *接口提供的缓冲以及获取其参数所有权的函数的后果。例如,如果您先使用 fileno() 再使用 fdopen(),那么您就犯了一个错误,因为句柄现在被两个不同的 FILE *对象所拥有。一般来说,选择一个API并坚持使用它更简单,主要取决于您是否需要缓冲以及是否需要使用内存映射或状态文件等。 - Dietrich Epp

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