views:

460

answers:

6

Sorry to keep hammering on this, but I'm trying to learn :). Is this any good? And yes, I care about memory leaks. I can't find a decent way of preallocating the char*, because there simply seems to be no cross-platform way.

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

UPDATE2: without Boost or Qt, the most common stuff can get long-winded (see accepted answer)

+1  A: 

You need to check for a_cwd being NULL. Then it will work on Mac, Windows, Linux. However, it's not POSIX-compliant.

EDIT: perror doesn't exit the program, so you should exit, throw an exception, or do something.

Matthew Flaschen
Got it, copy-pasting from MSDN :) And I think the POSIX standard is stupid here, as there is no way to securely allocate a char* that is guaranteed to be large enough >:s
rubenvb
@ruben, I don't agree. _PC_PATH_MAX is the recommended method. It's important to note that getcwd will never buffer overflow, since you pass it the size. At most it will give you a ERANGE if your buffer is too small. MSDN is not a reliable source on POSIX.
Matthew Flaschen
Never said anything about this needing to be POSIX. I'm dropping full POSIX compatibility here, and gladly :)
rubenvb
@ruben, you said "the POSIX standard is stupid here". What I pointed out is that if it only needed to work on POSIX, there's a recommended solution. Naturally working cross-platform adds complexity.
Matthew Flaschen
I understand, and am regretting my stubborn decision for "no boost or Qt" to keep my program nice and small. Thanks though.
rubenvb
+4  A: 

This will work on Windows and Linux, since they both support the automatic allocation behavior when the buf argument to getcwd is NULL. However, be aware that this behavior is not standard, so you may have issues on more esoteric platforms.

You can do it without relying on this behavior, though:

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;
}

The above code starts with a buffer size of 1024, and then, if getcwd complains that the buffer is too small, it doubles the size and tries again, and repeats until it has a large enough buffer and succeeds.

Note that calling realloc with its first argument as NULL is identical to malloc.

Tyler McHenry
This is way too long for a simple function call like this IMHO...
rubenvb
If you want to do it so that it conforms to POSIX standards and handles any arbitrary path length, this is what you have to do. If you want to make it shorter, you have to give up one of those two requirements.
Tyler McHenry
OK, this is where I toss POSIX overboard :)
rubenvb
You're free to do so, but since the emphasis of your question seemed to be on cross-platform portability, I would have expected you to put up with the extra dozen or so lines of code...
Tyler McHenry
Well, it may be a bit long, but one, you don't have to look at it, just call it, and two, unless the buffer isn't large enough, it should be virtually as efficient as the shorter version (and given that there's an I/O hit to get the actual directory, then the difference really shouldn't matter). Since you can basically write this and forget about it, I'd definitely say to go for the POSIX-compliant version.
Jonathan M Davis
I'm getting a "invalid conversion from void* to char*" on the realloc line with GCC 4.4...
rubenvb
Oops, yeah, in C++ there's a cast required (in C there isn't). Fixing...
Tyler McHenry
Great thanks :)
rubenvb
+1, but to be really compliant, `buf = realloc(buf, ...);` is bad, because if `realloc()` fails, you get a memory leak. So to be *really* pedantic, you should have something like `tmp = realloc(buf, ...); if (tmp) buf = tmp; else { /* handle error */ }`
Alok
That's unnecessarily long and it's not exception safe.
Joe Gauterin
@Joe: Care to enlighten me on how it should be done then?
rubenvb
Matteo Italia's answer below is actually, in my opinion, the most correct answer in terms of, efficiency, error handling, and exception safety. It's even longer than mine, though. :)
Tyler McHenry
OK, changing the accepted answer, sorry Tyler...
rubenvb
+3  A: 

If you want to remain standard, getcwd isn't required to do anything if you pass to it a NULL; you should instead allocate on the stack a buffer that is "large enough" for most occasions (say, 255 characters), but be prepared for the occasion in which getcwd may fail with errno==ERANGE; in that case you should allocate dinamically a bigger buffer, and increase its size if necessary.

Something like this could work (notice: not tested, just written by scratch, can be surely improved):

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");
}

By the way, in your code there's a very wrong thing: you are trying to dellocate a_cwd (which presumably, in the nonstandard extension, is allocated with malloc or with some other memory allocation function, since getcwd is thought for C) with delete: you absolutely shouldn't do that, keep in mind that each allocation method has its deallocation counterpart, and they must not be mismatched.

Matteo Italia
Two things: your first if is missing a ')' at the end, and about scoped_ptr: it won't be in c++0x, right? Is there any noticable impact of scoped_ptr vs auto_ptr in this case? Thanks
rubenvb
Would it be better to use c++0x/tr1 shared_ptr or would this make absolutely no difference?
rubenvb
In C++0x you should use unique_ptr, shared_ptr is overkill. For the missing ')', thank you, fixed.
Matteo Italia
+2  A: 

You must not pass a null pointer to the constructor of a std::string, so you must check the buffer pointer getcwd() returns isn't null. Also, the buffer pointer you pass to getcwd() must not be null.

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, ...
}
wilhelmtell
Would rather throw an exception on failure.
Martin York
+1  A: 

You're supposed to use the ISO C++ conformant version _getcwd I think. There's no point returning a const string, and you should use free to deallocate (at least according to MSDN):

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

Of course you should also check if _getcwd() returns NULL.

Alex - Aotea Studios
_getcwd is not POSIX-compliant. However, if you look at the previous question, ruben is covering this with an ifdef.
Matthew Flaschen
@Matthew: sorry, I meant to say ISO C++ conformant - corrected.
Alex - Aotea Studios
A: 

How about this? It's short, exception safe, and doesn't leak.

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')-1);
    return result;
}
Joe Gauterin