在析构函数中调用CloseHandle会导致运行时内存错误,即如何正确关闭结构/类中的文件句柄

calling CloseHandle in destructor causes runtime memory error, how to properly close a file handle in a struct/class?

本文关键字:何正确 结构 文件句柄 错误 CloseHandle 调用 析构函数 内存 运行时      更新时间:2023-10-16

错误消息为:"0x7c810eac"的指令引用了"0x00000000"的内存。无法"写入"内存。

如果我移除析构函数,一切都会好起来的。但我不明白这里发生了什么。无论我读到哪里,我都应该关闭句柄,但这里的代码不允许我。(是的,我知道我可以手动完成……但这是客户端代码中不必要的一行,我认为应该由对象处理。)

#include <windows.h>
#include <iostream>
#include <string>
struct fileHandle {
  HANDLE hFile;
  fileHandle(std::string path) {
    hFile = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
      printf("error: INVALID_HANDLE_VALUE");
    }
  }
  ~fileHandle() {
    CloseHandle(hFile);
  }
  void save(std::string data) {
    if (!WriteFile(hFile, data.c_str(), data.size(), NULL, NULL)) {
      printf("WriteFile failed: ", GetLastError());
    }
  }
};
int main() {
  fileHandle my_handle("test_file.txt");
  my_handle.save("some text");
}

更新:当文件不存在时会发生这种情况。当文件确实存在程序打印错误时,但这是有意的。我在这里只要求共同覆盖这个文件创建时的情况(我知道如何重写处理创建以覆盖现有文件。)

编译器:http://sourceforge.net/projects/mingwbuilds/files/host-windows/releases/4.7.2/32-bit/threads-posix/sjlj/x32-4.7.2-release-posix-sjlj-rev7.7z

更新2:我并没有提到这个代码可以工作并写入文件。内存错误在最后触发。

请使用-Wall编译所有代码。节省了大量时间

在您的代码printf中,它具有无效的格式字符串。printf的正确解决方案是(注意%lu):

void save(std::string data) {
  if (!WriteFile(hFile, data.c_str(), data.size(), NULL, NULL)) {
    printf("WriteFile failed: %lu", GetLastError());
  }
}

如果您使用-Wall进行编译,那么您的代码会发出警告:

filehandle.cpp: In member function 'void fileHandle::save(std::string)':
filehandle.cpp:18:50: warning: too many arguments for format [-Wformat-extra-args]

此外,您应该将错误打印到stderr,因为它没有缓冲区。printf使用缓冲区,这就是为什么您没有得到任何输出。在错误后添加n也是一个好主意。

同时阅读其他答案以改进代码。使用三条规则。


看完评论后,我意识到这确实不是segfault的原因。(也可以看看Ron Burk的解决方案,看看哪里出了问题。)

根据Windows API文档,只有当lpOverlapped parameter不是NULL时,lpNumberOfBytesWritten参数才可以是NULL

因此,您必须提供一个指向DWORD的指针,WriteFile可以在其中存储它实际读取的字节数。最后的保存是:

void save(std::string data) {
  DWORD writtenBytes;
  if (!WriteFile(hFile, data.c_str(), data.size(), &writtenBytes, NULL)) {
    printf("WriteFile failed: %lu", GetLastError());
  }
}

如果文件存在,则不会弹出错误,因为将INVALID_HANDLE_VALUE传递给WriteFile似乎会使WriteFile的返回时间早于它使用指针的时间。

WriteFile()的参数错误。倒数第二个参数不能为NULL。将其更改为您已设置为0的DWORD地址,一切都将正常工作。当它试图写回写入的字节数时,它在WriteFile()中的内核中正在消亡。

CloseHandle的文档清楚地说明了发生这种情况的原因:

如果应用程序在调试器下运行,则如果函数接收到无效的句柄值或伪句柄值,则函数将引发异常。

因此,当对CreateFile的调用失败时,对CloseHandle的后续调用将引发SEH异常。

解决方案是,如果对CreateFile的调用成功,则代码必须仅调用CloseHandle。

正如其他人所指出的,您对WriteFile的使用是错误的。这里我不再赘述细节。

~fileHandle() {
    if(hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);
  }

您需要检查文件是否正确打开。

 if (hFile != INVALID_HANDLE_VALUE) CloseFile(hFile);

您没有遵循三原则,因此hFile的所有权在fileHandle的副本之间共享,因为编译器生成的复制构造函数和复制赋值运算符很浅。

例如:

fileHandle my_handle("test_file.txt");
fileHandle my_second_handle = my_handle;

其中哪一个应该真正关闭销毁手柄?第一个?当第二个超出作用域并调用析构函数时会发生什么?

在这种情况下,我认为您应该禁止复制或分配,或者明确所有权。

编辑:在你的片段中,问题可能是罗恩指出的,但这仍然是一个重要的问题。

不过我不会这么对待它我认为如果文件无法打开,fileHandle就不应该存在。拥有一个无效对象有什么意义?我只需要在构造函数中抛出一个异常来禁止无效对象,而不是在析构函数中进行检查。

fileHandle(std::string path) {
    hFile = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
       throw std::exception("file not found");
    }
  }

为了安全起见,请使用

~fileHandle() 
{
            if (hfile != INVALID_HANDLE_VALUE)
            {
                CloseHandle(hfile);
                hfile = INVALID_HANDLE_VALUE;
            }
}