Как вернуть std :: string из функции getcwd в C - PullRequest
3 голосов
/ 20 мая 2010

Извините, что продолжаю вникать в это, но я пытаюсь научиться :). Это хорошо? И да, меня волнуют утечки памяти. Я не могу найти приличный способ предварительного выделения char *, потому что, похоже, просто нет кроссплатформенного способа.

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

ОБНОВЛЕНИЕ2: без Boost или Qt, наиболее распространенные вещи могут быть затянуты (см. Принятый ответ)

Ответы [ 7 ]

7 голосов
/ 20 мая 2010

Если вы хотите остаться стандартным, 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");
}

Кстати, в вашем коде есть очень неправильная вещь: вы пытаетесь выделить a_cwd (который, по-видимому, в нестандартном расширении выделяется с помощью malloc или с какой-либо другой функцией выделения памяти, поскольку getcwd предназначен для C) с delete: вы абсолютно не должны этого делать, имейте в виду, что каждый метод выделения имеет свой аналог освобождения, и они не должны быть несовпадающими.

3 голосов
/ 20 мая 2010

Это будет работать в Windows и Linux, так как они оба поддерживают режим автоматического выделения, когда аргумент buf для getcwd равен NULL. Однако помните, что это поведение не стандартное, поэтому у вас могут быть проблемы на более эзотерических платформах.

Вы можете сделать это, не полагаясь на это поведение, однако:

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 жалуется, что буфер слишком мал, он удваивает размер и пытается снова, и повторяется до тех пор, пока не получит достаточно большой буфер и не выполнится успешно. 1013 *

Обратите внимание, что вызов realloc с его первым аргументом в качестве NULL идентичен malloc.

2 голосов
/ 20 мая 2010

Вы должны использовать версию, совместимую с 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.

2 голосов
/ 20 мая 2010

Вы не должны передавать нулевой указатель в конструктор 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 голос
/ 21 мая 2010

Как насчет этого?Это коротко, исключение безопасно, и не протекает.

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 голос
/ 20 мая 2010

Вам нужно проверить, что a_cwd равен NULL. Тогда он будет работать на Mac, Windows, Linux. Тем не менее, он не POSIX-совместимый.

РЕДАКТИРОВАТЬ: perror не выходит из программы, поэтому вы должны выйти, вызвать исключение или что-то сделать.

0 голосов
/ 26 апреля 2019

Когда «струнный конструктор» сделает все за вас:

#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());
}
...