c++ 为什么在git bash上执行程序时freopen函数不能正常运行?

ddrv8njm  于 2023-03-20  发布在  Git
关注(0)|答案(1)|浏览(142)
#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    auto file = freopen("./input.txt", "r", stdin);

    if (file == nullptr) {
        cout << "freopen failed" << '\n';
    } else {
        int n;
        cin >> n;
        cout << n << endl;
    }

    return 0;
}

这个程序读取一个文件中的整数并输出它。它可以在其他shell上运行,如cmd,powershell和Msys 2 bash。但是在git bash上,无论输入文件是什么,它总是输出0。当freopen用于stdout时,它也会失败。
我用MSYS 2下载的g++.exe (Rev10, Built by MSYS2 project) 12.2.0编译。
实际上,我找到了解决这个问题的方法,我把libstdc++-6.dll复制到MSYS的mingw 64(确切地说是msys 2/ucrt 64/bin),然后把libstdc++-6.dll重写到git的mingw 64,它就开始工作了,看起来好像当程序在git bash上执行时,它先链接了git的mingw 64的dll,导致freopen无法工作。
然而,我仍然想知道为什么会发生这种情况摆在首位,以及我的解决方案是否可能在未来造成问题。

  1. git-for-windows的libstdc++是否有bug?如果没有,问题的原因是什么?git bash是否只用于使用git,而不是用于运行可执行文件之类的?
    1.我的解决方案是否有可能导致另一个问题?如果有,是否有其他更好的解决方案?
a7qyws3x

a7qyws3x1#

问题是stdincin是底层操作系统例程的缓冲I/O Package 器,并且彼此独立(这也是为什么不应该将它们混合用于输入,因为它们可能缓冲读取的一部分)。
freopen()将替换stdin Package 器,但底层操作系统文件描述符和/或句柄不一定会被替换(在您的系统上也不会),因此cin仍将连接到底层句柄。
据我所知,在C++中没有可移植的方法来更改cin
但是,如果您只想支持最常用的操作系统,则有以下几种可能性:

**POSIX:**在基于POSIX的操作系统上,如Linux、macOS、FreeBSD等,您可以使用底层的open()函数(#include <fcntl.h>)打开文件,然后使用dup2()替换文件描述符0,它是标准的输入文件描述符。

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <stdexcept>
// for std::format, C++20 and higher
#include <format>

int fd = open(fileName, O_RDONLY);
if (fd < 0) {
    int error = errno;
    throw std::runtime_error(std::format("Couldn't open file: {}", strerror(error)));
}
int r = dup2(fd, 0);
if (r < 0) {
    int error = errno;
    close(fd);
    throw std::runtime_error(std::format("Couldn't replace input file descriptor: {}", strerror(error)));
}
// Close the original opened descriptor
close(fd);

(Maven技术说明:如果您的操作系统支持O_CLOEXEC,您可能还希望将open()传递给open()的标志,但请确保dup2()也清除您的操作系统上的该标志,它在Linux上会清除该标志,因为标准描述符不应设置该标志。由于不提供该标志,此实现存在微小的争用情况,但由于我们在调用dup2()后立即关闭fd,我个人对此没有意见,因此在多线程环境中通常不会替换标准输入描述符。)

**Windows:**在Windows上要复杂一些。你可以使用SetStdHandle() Win32 API函数来替换输入文件句柄本身,但这对C运行时没有帮助(在某些情况下,C运行时也是C++运行时的基础),因为它有一个基础句柄的副本。所以你还需要使用_open_osfhandle()从句柄中获取文件描述符,并使用_dup2()复制它(类似于POSIX版本,但这里的文件描述符只针对C运行时本身)。

下面的代码应该可以在Windows上运行:

#include <windows.h>
#include <fcntl.h>

#include <stdexcept>
// for std::format, C++20 and higher
#include <format>

// This is so that the handle can be inherited by
// child processes.
SECURITY_ATTRIBUTES attrs{};
attrs.nLength = sizeof(attrs);
attrs.lpSecurityDescriptor = nullptr;
attrs.bInheritHandle = TRUE;
// use CreateFileW if fileName is a Unicode wide string
HANDLE handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, &attrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    throw std::runtime_error(std::format("Couldn't open file: error code {:d}", error));
}
BOOL ok = SetStdHandle(STD_INPUT_HANDLE, handle);
if (!ok) {
    DWORD error = GetLastError();
    CloseHandle(handle);
    throw std::runtime_error(std::format("Couldn't use handle as standard input: error code {:d}", error));
}
/* _open_osfhandle will take ownership of the handle itself, so we
 * have to provide it with a duplicate, because we call _close()
 * at the bottom, and we don't want the handle we passed to
 * SetStdHandle() to be closed, because that causes some weird
 * failures in some weird places
 */
HANDLE handleForDescriptor{INVALID_HANDLE_VALUE};
ok = DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &handleForDescriptor, 0, FALSE, DUPLICATE_SAME_ACCESS);
if (!ok) {
    DWORD error = GetLastError();
    /* Don't CloseHandle() here, because we already used
     * SetStdHandle, so the handle is already part of the
     * global state.
     */
    throw std::runtime_error(std::format("Couldn't duplicate handle: error code {:d}", error));
}
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(handleForDescriptor), _O_RDONLY | _O_TEXT);
if (fd < 0) {
    DWORD error = GetLastError();
    CloseHandle(handleForDescriptor);
    throw std::runtime_error(std::format("Couldn't get file descriptor for handle: error code {:d}", error));
}
int r = _dup2(fd, 0);
if (r < 0) {
    DWORD error = GetLastError();
    _close(fd);
    throw std::runtime_error(std::format("Couldn't get file descriptor for handle: error code {:d}", error));
}
_close(fd);
    • 警告**:只有当您使用的C运行时与使用cin/stdin的所有代码相同时,此操作才有效。例如,如果您使用msvcrt.dll作为项目的C运行时,但您使用的函数来自使用msvcr120.dll本身的DLL,则该DLL不会看到更改,因为它使用的不同的C运行时会跟踪不同的内部文件描述符。如果您使用相同的编译器编译此代码和从cin读取数据的代码,这应该不是问题,但是如果您运行的是第三方代码,而您没有使用编译器显式地重新编译这些代码,那么我不知道有什么简单的方法可以绕过这种限制。

免责声明:我没有明确测试过,我有类似的代码来替换stderr,我在这里做了修改。我可能在翻译它的时候犯了一些错误。

相关问题