【问题标题】:Why does fstream.open(filename) work with literal but not with generated string? [duplicate]为什么 fstream.open(filename) 使用文字而不使用生成的字符串? [复制]
【发布时间】:2014-01-28 04:00:19
【问题描述】:

在使用 MS Visual Studio 2003 从 C++ 编译的 DLL 中,我通过从 SHGetFolderPath 获取字符串并附加文件名来生成文件路径,然后尝试打开该文件进行输出。

我有一些全局变量,一个用于设置文件路径的函数,另一个用于打开文件的函数,如下所示。
Logger 类声明未显示,但它有一个声明为 ofstream *ofs 的成员,这是在 Logger 的构造函数中创建的文件对象。

constructorBase() 中,如果我调用initLogFN() 来设置文件路径,ofs->open 会失败。
但是,如果我注释掉 initLogFN() 调用并取消注释将 logPath 设置为硬编码字符串文字的行,ofs->open 会成功。

在任何一种情况下进行调试时,Visual Studio 都会在调用 ofs->open 之前正确地将 logPath 显示为具有值“C:\Users\sleis\AppData\Roaming\Address-IT.log”。
然而,在失败的情况下,在ofs->open 调用之后,logPath 的值更改为六个字符,这些字符看起来是随机的,但每次运行我的测试应用程序时都是相同的。

为什么这两种情况的行为不同,我该如何解决失败的情况?


#if defined (WIN32)
const char dirSep[]="\\";
#else
const char dirSep[]="/";
#endif

const char logFN[]="Address-IT.log";
char *logPath=0;

bool initLogFN()
{
    if (logPath!=0) return false;

#if defined (_DEBUG)
#if defined (WIN32)
    char path[MAX_PATH];
    HRESULT result=SHGetFolderPath(NULL,CSIDL_APPDATA,NULL,SHGFP_TYPE_CURRENT,path);
    if (result!=S_OK) return false;
    strcat(path,dirSep);
    strcat(path,logFN);
    logPath=path;
    return true;
#endif
#endif

    return false;
}

void Logger::constructorBase()
{
    if (!initLogFN()) return;
    //logPath="C:\\Users\\sleis\\AppData\\Roaming\\Address-IT.log";

    ofs->open(logPath,ios_base::out | ios_base::app);

    if ((ofs->rdstate() & std::ifstream::failbit)!=0) {
        std::cout << std::endl << "Error opening log file.";
    } else if (ofs->is_open()) {
        std::cout << std::endl << "Log file opened successfully.";
    }
}

【问题讨论】:

    标签: c++ string fstream


    【解决方案1】:

    数组pathinitLogFN 的局部自动变量。当您设置logPath=path 时,它会导致logPath 变量指向该数组。但是当函数返回时,该数组超出范围,这意味着它占用的任何内存都可能随后被覆盖。现在path 指向垃圾。

    永远不要返回指向局部变量的指针。

    从函数返回 C 风格的字符串基本上有三个选项:

    1. 使用malloc 或(在C++ 中)new 让函数动态分配 字符串的内存。如果你这样做,那么调用者最终将不得不调用freedelete 以避免内存泄漏。

    2. char* 作为参数传递给函数。调用者应该为字符串预先分配内存,然后将指针传递给函数。该函数写入指针指向的位置。

    3. 使用全局变量,并返回指向该变量的指针。

    在这些方法中,#2 是最常见的。 C 和 C++ 标准库采用这种方法(例如fgets),并且通常需要另一个参数来指示传入的缓冲区大小,因此函数知道不会写入超过分配内存的末尾。

    方法#3 的缺点是它不可重入。一些旧的 Unix 库函数采用方法 #3(例如ttyname),但后来引入了采用方法 #2 的可重入版本(例如,ttyname_r)。

    【讨论】:

    • 这个答案对我来说似乎最清楚,但我也给其他人投了赞成票。代码现在可以运行了,谢谢。
    【解决方案2】:

    问题是您试图引用一个不再存在的变量 (char path[MAX_PATH])。

    首先您在 initLogFN 中创建此变量。然后你指定 logPath 指向它的地址。但是在 initLogFN 的范围之外(也就是在它返回之后),现在指向的内存 logPath 是完全垃圾。

    在您调用下一个函数之前在调试器中它看起来仍然正常的原因是因为它存在于堆栈中。但是,堆栈被对下一个函数的调用覆盖。而你的字符串充满了垃圾。为什么每次都填满相同的六个字符的垃圾?让我们称之为运气。

    有几种方法可以解决此问题。

    1. 您可以动态分配字符数组,例如说char* path = new char[MAX_PATH]。如果您这样做,请确保在完成后通过说 delete path 将其清理干净。
    2. 您可以在构造函数库中声明 path,然后将其传递给您的 initLogFN。例如,您的新 initLogFN 将具有此签名 bool initLogFN(char* path),您可以这样称呼它(来自 Logger::constructorBase)char path[MAX_PATH]; initLogFN(path);
    3. 第三种方法是删除这个看似不必要的辅助函数 (initLogFN),然后将该代码放入 constructorBase 函数中。

    选择权在你,你自己!祝你好运!请记住,如果您不动态分配某些东西(使用 newma​​lloc),那么不要尝试存储指向它的全局指针,而且绝对不要尝试在声明它的范围之外引用它!

    【讨论】:

      【解决方案3】:

      语句logPath=path; 将指针logPath 设置为指向与路径相同的位置。一旦函数退出,path 的内存(以及 logPath 的内存)就会被释放,内存会被重新分配并被进一步的函数调用覆盖。

      您需要为 logPath 分配数据并使用 strcpy 将数据从 path 复制到 logpath 以防止这种情况发生。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-15
        • 1970-01-01
        • 2015-06-16
        • 1970-01-01
        • 2016-12-30
        • 1970-01-01
        • 1970-01-01
        • 2017-05-12
        相关资源
        最近更新 更多