2008-11-12 2 views
2

У меня есть dll, который должен использоваться с C и т. Д., Поэтому я не могу использовать строковые объекты и т. Д., Как обычно, но я не уверен, как это сделать безопасно.Вернуть динамически выделенную память с C++ на C

const char *GetString() 
{ 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    return ss.str().c_str(); 
} 

Может ли c строка быть уничтожена, когда ss падает со стека? Я предполагаю, что ...

Другой вариант может заключаться в том, чтобы создать новую строку в куче, но что же будет освобождать?

const char *GetString() 
{ 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    char *out = new char[ss.str().size()]; 
    strcpy(ss.str().c_str(), out); 
    return out;//is out ever deleted? 
} 

То же самое касается указателей на другие вещи, а также на строки.

ответ

8

Первый вариант не работает, потому что вы возвращающая указатель в стек, который будет уничтожен.(Более точно, вы возвращаете указатель на кучную память, которая будет удалена().) Хуже того, это может даже работать некоторое время, если никто не перезаписывает память, что делает ее очень трудной для отладки.

Далее, вы не можете вернуть константный символ *, если не возвращает указатель на статическую строку, как это:

const char *GetString() 
{ 
    return "a static string in DATA segment - no need to delete"; 
} 

Вы второй вариант имеет проблему возвращения памяти, выделенной с новым() в C которая вызовет free(). Возможно, они не совместимы.

Если вы возвращаете строку в C, есть два способа сделать это:

char *GetString() 
{ 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    return strdup(ss.str().c_str()); // allocated in C style with malloc() 
} 

void foo() 
{ 
    char *p = GetString(); 
    printf("string: %s", p)); 
    free(p); // must not forget to free(), must not use delete() 
} 

или:

char *GetString(char *buffer, size_t len) 
{ 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    return strncpy(buffer, ss.str().c_str(), len); // caller allocates memory 
} 

void foo() 
{ 
    char buffer[ 100 ]; 
    printf("string: %s", GetString(buffer, sizeof(buffer))); // no memory leaks 
} 

в зависимости от вас памяти обработки политики.

Как правило, вы никогда не можете вернуть указатель или ссылку на автоматический объект на C++. Это одна из распространенных ошибок, проанализированных во многих книгах на C++.

1

Первый на самом деле не работает, потому что stringstream освобождает пространство от разрушения. Поэтому, если вы попытаетесь отменить ссылку на этот указатель, есть хороший шанс, что ваша программа потерпит крах.

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

Другой вариант - вернуть адрес статического массива символов. Это актуально, если вы заранее знаете хорошую верхнюю границу длины. Что еще более важно, это следует использовать ТОЛЬКО, если нет возможности, что функция будет вызываться из двух разных потоков одновременно, потому что использование статического массива по существу делает вашу функцию не reentrant.

+0

Хорошо, хорошо, что моя dll является actauly для python, так что лучший способ сделать это? Должен ли я обернуть функции dll в функции python, которые говорят: «Call dll function; Call dll deallocate function»? Я предполагаю, что python делает полностью новую строку, а не просто обертывает вокруг нее объект? – 2008-11-12 08:30:26

1

Очевидно, что в любое время, когда вы возвращаете указатели на память, выделенные внутри функции, освобождение должно происходить извне, если вы не используете сбор мусора. Если вы не хотите этого делать, выделите буфер символов до вызова GetString() и измените прототип на

int get_string (const char * buffer);

Затем заполните буфер. Но возвращение точки к malloced данным в порядке.

+0

и, пожалуйста, пройдите также размер буфера! – quinmars 2008-11-12 08:52:38

+0

int get_string (const char * buffer, int bufferSize); <- так ... – xan 2008-11-12 09:27:26

0

Если вы заявляете ss как static, вы можете избежать проблемы. Это может быть хорошим решением, если ваша программа работает в среде с одним потоком.

+0

Но результат становится недействительным, как только функция вызывается во второй раз. С этого момента действителен только результат второго вызова. – wimh 2008-11-12 08:58:31

0

Вам необходимо выделить строку в куче, если вы хотите ее безопасно вернуть, а также выделить с помощью malloc() i.s.o. new() при записи функций C.

Когда вы возвращаете указатели (и, в отличие от C++, на C, у вас нет реального выбора много раз), освобождение всегда вызывает беспокойство. На самом деле не существует окончательного решения.

Один из способов обработки этого я видел в довольно некоторых API, звонят все функции либо

CreateString() 

Когда память должна быть высвобождена вызывающим и

GetString() 

, когда это не вопрос.

Это ничего, кроме Foolproof конечно, но, учитывая достаточно дисциплины, что это лучший метод, который я видел, если честно ...

0

Если потокобезопасность не важно,

const char *GetString() 
{ 
    static char *out; 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    delete[] out; 
    char *out = new char[ss.str().size()]; 
    strcpy(ss.str().c_str(), out); 
    return out;//is out ever deleted? 
} 

Тогда функция может взять на себя ответственность за deallocating строки.

Если нить-безопасность важна,

Тогда лучший способ, чтобы передать его в качестве аргумента, как,

void GetString(char *out, int maxlen); 

Я наблюдаю это то, что происходит, когда старый нон резьбовых безопасные API заменены на потокобезопасные.

0

После того, как функция вызывается, вы хотите, чтобы вызывающий абонент отвечал за память строки (и особенно за ее выделение). Если вы не хотите использовать статические переменные, но там есть драконы! Лучший способ сделать это чисто, чтобы иметь вызывающий сделать выделение памяти в первую очередь:

void foo() { 
    char result[64]; 
    GetString(result, sizeof(result)); 
    puts(result); 
}

, а затем GetString должен выглядеть следующим образом:

int GetString(char * dst, size_t len) { 
    std::stringstream ss; 
    ss << "The random number is: " << rand(); 
    strncpy(ss.str().c_str(), dst, len); 
}

Переходя максимальную длину буфера и использование strncpy() позволит избежать случайной перезаписи буфера.

3

На протяжении многих лет С вареной это до 2 стандартных методов:

  • вызывающего абонента проходит в буфере.
    Существует три варианта этого.
    Версия 1: Передайте буфер и длину.
    Версия 2: Документация указывает размер ожидаемого минимального буфера.
    Версия 3: Предполетный. Функция возвращает требуемый буфер. звонящий звонит дважды в первый раз с помощью NULL-буфера.
    • Пример: чтение()
  • Используйте статический буфер, который действует до следующего вызова.
    • Пример: tmpname()

Некоторые нестандартные из них возвращается память, что вы должны были явно свободный

  • strdup() выскакивает на ум.
    Общее расширение, но не стандартно.
0

Ответы до сих пор не затрагивают очень значительную проблему, а именно, что делать, если длина требуемого буфера для результата неизвестна и может меняться между вызовами, даже с теми же аргументами (например, чтением значение из базы данных), поэтому я предоставляю то, что считаю лучшим способом справиться с этой ситуацией.

Если размер не известен заранее, рассмотреть вопрос о принятии функцию обратного вызова для вашей функции, которая принимает const char* в качестве параметра:

typedef void (*ResultCallback)(void* context, const char* result); 

void Foo(ResultCallback resultCallback, void* context) 
{ 
    std::string s = "...."; 
    resultCallback(context, s.c_str()); 
} 

Реализация ResultCallback может выделить память, необходимую и скопировать в буфер указана result. Я предполагаю C, поэтому я не нахожусь в/из void* явно.

void UserCallback(void* context, const char* result) 
{ 
    char** copied = context; 
    *copied = malloc(strlen(result)+1); 
    strcpy(*copied, result); 
} 

void User() 
{ 
    char* result = NULL; 

    Foo(UserCallback, &result); 

    // Use result... 
    if(result != NULL) 
     printf("%s", result); 

    free(result); 
} 

Это самое портативное решение и обрабатывает даже самые сложные случаи, когда размер возвращаемой строки не могут быть известны заранее.

0

Существуют различные методы, разработанные с течением времени, для возврата переменного объема данных из функции.

  1. Caller проходит в буфере.
    1. необходимому размер документируются и не прошел, слишком короткие буфера Undefined Behavior: strcpy()
    2. необходимому размер документируются и передается, ошибки сигнализируют возвращаемое значение: strcpy_s()
    3. необходимого размера неизвестен, но может быть вызвано вызовом функции с длиной буфера 0: snprintf
    4. Необходимый размер неизвестен и не может быть запрошен, возвращается столько же, сколько помещается в буфер переданного размера. Если дополнительные необходимые вызовы должны быть сделаны, чтобы получить остальные: fread
    5. размер необходимого неизвестно, не может быть запрошен, и переходя слишком маленький буфер Undefined Behavior. Это дефект дизайна, поэтому функция устарела/удалена в более новых версиях и только что упомянута здесь для полноты: gets.
  2. Caller передает функцию обратного вызова:
    1. Обработчик-функция получает контекстное параметр: qsort_s
    2. обратного вызова функция не получает контекстно-параметров. Получение контекста требует магии: qsort
  3. Caller передает распределитель: не найден в стандартной библиотеке C. Тем не менее, все поддерживаемые распределителем контейнеры C++ поддерживают это.
  4. Контракт Callee определяет освободитель. Вызов неправильный является Undefined Behavior: fopen ->fclosestrdup ->free
  5. вызываемая сторона возвращает объект, который содержит deallocator: COM-объекты std::shared_ptr
  6. вызываемая сторона использует внутренний общий буфер: asctime

В общем , когда пользователь должен угадать размер или посмотреть его в руководстве, он иногда ошибается. Если он не ошибается, более поздний вариант может привести к недействительности его тщательной работы, поэтому не имеет значения, что он когда-то был прав. Так или иначе, этот путь лежит madness (UB).

Для отдыха выберите наиболее удобный и эффективный способ.

Смежные вопросы