如何从C语言的"getcwd"函数中返回一个std::string?

4

抱歉一直在强调这个问题,但我正在努力学习:)。这样行吗?是的,我关心内存泄漏。我找不到一个合适的预分配char*的方法,因为似乎没有跨平台的方法。

const string getcwd()
{
    char* a_cwd = getcwd(NULL,0);
    string s_cwd(a_cwd);
    free(a_cwd);
    return s_cwd;
}

更新2:没有使用Boost或Qt,最常见的东西可能会变得冗长(请参见被接受的答案)。

请参考rubenvb在此问题上的先前提问 - Matthew Flaschen
CAKE_getcwd是什么?谷歌找不到相关信息。在Solaris上,普通的getcwd不能按照你的期望工作。虽然当你传递一个空指针时它会分配内存,但它分配的内存量完全基于第二个getcwd参数,而不是它想要返回的字符串长度。因此,在你的例子中,它将分配字节(或者只是失败并返回EINVAL)。 - Rob Kennedy
CAKE_getcwd是我在Windows上解决getcwd与_getcwd弃用警告的替代定义。已修复。 - rubenvb
个人而言,我更喜欢比“关于主题的问题”更具信息量的标题。因为我们知道这是一个问题,也知道它的主题是什么,所以具体的标题更好 :) - Paul
标题已经修正,将来会尽量更加详细地描述。 - rubenvb
7个回答

8
如果您想保持标准,getcwd如果传递给它一个NULL,不需要执行任何操作;相反,您应该在堆栈上分配一个“足够大”的缓冲区(比如255个字符),但要为可能发生的getcwd失败的情况做好准备,此时errno==ERANGE;在这种情况下,您应该动态分配一个更大的缓冲区,并在必要时增加其大小。

类似这样的代码可以工作(注意:未经测试,只是从头开始编写,肯定可以改进):

string getcwd()
{
    const size_t chunkSize=255;
    const int maxChunks=10240; // 2550 KiBs of current path are more than enough

    char stackBuffer[chunkSize]; // Stack buffer for the "normal" case
    if(getcwd(stackBuffer,sizeof(stackBuffer))!=NULL)
        return stackBuffer;
    if(errno!=ERANGE)
    {
        // It's not ERANGE, so we don't know how to handle it
        throw std::runtime_error("Cannot determine the current path.");
        // Of course you may choose a different error reporting method
    }
    // Ok, the stack buffer isn't long enough; fallback to heap allocation
    for(int chunks=2; chunks<maxChunks ; chunks++)
    {
        // With boost use scoped_ptr; in C++0x, use unique_ptr
        // If you want to be less C++ but more efficient you may want to use realloc
        std::auto_ptr<char> cwd(new char[chunkSize*chunks]); 
        if(getcwd(cwd.get(),chunkSize*chunks)!=NULL)
            return cwd.get();
        if(errno!=ERANGE)
        {
            // It's not ERANGE, so we don't know how to handle it
            throw std::runtime_error("Cannot determine the current path.");
            // Of course you may choose a different error reporting method
        }   
    }
    throw std::runtime_error("Cannot determine the current path; the path is apparently unreasonably long");
}

顺便提一下,在你的代码中有一个非常错误的地方:你试图使用delete释放a_cwd(在非标准扩展中,它可能是使用malloc或其他内存分配函数分配的,因为getcwd是为C而设计的),你绝对不应该这样做,记住每种分配方法都有其相应的释放方法,它们不能不匹配。


两件事情:你的第一个if语句末尾缺少一个')',关于scoped_ptr:它不会出现在c++0x中,对于这种情况,scoped_ptr与auto_ptr之间有什么明显的影响吗?谢谢。 - rubenvb
使用c++0x/tr1 shared_ptr是否更好,还是完全没有区别? - rubenvb
在C++0x中,你应该使用unique_ptr,shared_ptr是过度设计。感谢指出缺少的')',已修复。 - Matteo Italia

3

当传递给getcwd函数的buf参数为NULL时,Windows和Linux都支持自动分配内存的行为,因此这将在这两个系统上正常工作。但是,请注意,这种行为不是标准行为,因此您可能在更加小众的平台上遇到问题。

但是,您也可以不依赖于这种行为来完成操作:

const string getcwd()
{
    size_t buf_size = 1024;
    char* buf = NULL;
    char* r_buf;

    do {
      buf = static_cast<char*>(realloc(buf, buf_size));
      r_buf = getcwd(buf, buf_size);
      if (!r_buf) {
        if (errno == ERANGE) {
          buf_size *= 2;
        } else {
          free(buf);
          throw std::runtime_error(); 
          // Or some other error handling code
        }
      }
    } while (!r_buf);

    string str(buf);
    free(buf);
    return str;
}

上述代码开始时使用大小为1024的缓冲区,然后如果getcwd抱怨缓冲区太小,它会将缓冲区大小加倍并重试,直到有足够大的缓冲区并成功为止。
请注意,将realloc的第一个参数调用NULL与malloc相同。

如果您希望它符合POSIX标准并处理任意路径长度,那么这就是您必须要做的。如果您想让它更短,您就必须放弃这两个要求中的一个。 - Tyler McHenry
好的,这就是我抛弃POSIX的地方 :) - rubenvb
2
你可以这样做,但由于你的问题似乎强调跨平台可移植性,我本来期望你能忍受多出十几行代码... - Tyler McHenry
3
+1,但是为了真正地符合要求,buf = realloc(buf, ...); 是不好的,因为如果 realloc() 失败,你会得到一个内存泄漏。所以为了真正严谨,你应该像这样做:tmp = realloc(buf, ...); if (tmp) buf = tmp; else { /* 处理错误 */ } - Alok Singhal
正如其他人所提到的,realloc() 可能会返回一个空指针。必须进行检查。 - Alexis Wilke
显示剩余4条评论

3

在构造函数中,您不能传递空指针给std::string,因此必须检查getcwd()返回的缓冲区指针不为空。另外,您传递给getcwd()的缓冲区指针不能为空

std::string getcwd() {
    char buf[FILENAME_MAX];
    char* succ = getcwd(buf, FILENAME_MAX);
    if( succ ) return std::string(succ);
    return "";  // raise a flag, throw an exception, ...
}

1
在失败的情况下,宁愿抛出异常。 - Martin York

2

我认为你应该使用符合ISO C++标准的版本_getcwd。返回const string没有意义,而且你应该使用free进行释放(至少根据MSDN的说法):

string getcwd()
{
    char* a_cwd = _getcwd(NULL, 0);
    string s_cwd(a_cwd);
    free(a_cwd);
    return s_cwd;
}

当然,您还应该检查_getcwd()是否返回NULL。

1
getcwd不符合POSIX标准。然而,如果您查看之前的问题,ruben正在使用ifdef来解决这个问题。 - Matthew Flaschen
@Matthew:抱歉,我的意思是符合ISO C++标准 - 已更正。 - Alex Korban

1
这个怎么样?它很短,异常安全,并且不会泄漏。
std::string getcwd() {
    std::string result(1024,'\0');
    while( getcwd(&result[0], result.size()) == 0) {
        if( errno != ERANGE ) {
          throw std::runtime_error(strerror(errno));
        }
        result.resize(result.size()*2);
    }   
    result.resize(result.find('\0'));
    return result;
}

差一點我就會給你一個 +1 了,但不幸的是你在 result.resize(result.find('\0')-1); 這行程式碼中犯了一個 -1 的錯誤,因此你的結果是錯誤的... - Alexis Wilke

1

你需要检查a_cwd是否为NULL。然后它将在Mac、Windows和Linux上工作。但是,它不符合POSIX标准。

编辑:perror不会退出程序,所以你应该退出、抛出异常或者做一些其他的操作。


明白了,从MSDN复制粘贴 :) 我认为POSIX标准在这里很愚蠢,因为没有办法安全地分配足够大的char* >:s - rubenvb
@ruben,我不同意。_PC_PATH_MAX是推荐的方法。需要注意的是,由于您传递了大小,因此getcwd永远不会缓冲区溢出。最多会在缓冲区太小的情况下给您一个ERANGE。MSDN不是关于POSIX的可靠来源。 - Matthew Flaschen
从来没有说过这需要是POSIX。我在这里放弃了完全的POSIX兼容性,而且很高兴 :) - rubenvb
@ruben,你说“这里的POSIX标准很愚蠢”。我指出的是,如果它只需要在POSIX上工作,那么有一个推荐的解决方案。自然地,跨平台工作会增加复杂性。 - Matthew Flaschen
我了解,并且为我的“不使用Boost或Qt来保持程序简洁”的固执决定感到后悔。谢谢。 - rubenvb

0

当“字符串构造函数”为您完成一切时:

#include <stdio.h>  // defines FILENAME_MAX
#include <unistd.h> // for getcwd()

std::string GetCurrentWorkingDir()
{
    std::string cwd("\0",FILENAME_MAX+1);
    return getcwd(&cwd[0],cwd.capacity());
}

似乎不太安全:“与PATH_MAX不同,即使没有实际的限制,该宏也会被定义。在这种情况下,其值通常是一个非常大的数字。在GNU/Hurd系统上总是如此。” 我不想为微不足道的结果分配可能很大的内存量。 - rubenvb
谢谢您指出这一点。您关于大数字的观点是完全正确的,我在我的程序中使用了这种方法,因为我没有这样的限制。这个解决方案也适用于Linux/Windows。 - IluxaKuk
1
@lluxaKuk 嗯,问题在于这个常量可能会在未来的 glibc 和 Visual Studio 版本中发生变化,所以依赖它似乎是一件危险的事情。 - rubenvb

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