Вернуть динамически выделенную память из C ++ в C - PullRequest
2 голосов
/ 12 ноября 2008

У меня есть dll, которую нужно использовать из 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?
}

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

Ответы [ 10 ]

8 голосов
/ 12 ноября 2008

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

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

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

У вашего второго варианта есть проблема возврата памяти, выделенной с помощью new (), в программу на C, которая будет вызывать free (). Они могут быть несовместимы.

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

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 ++. Это одна из распространенных ошибок, проанализированных во многих книгах по С ++.

3 голосов
/ 12 ноября 2008

За эти годы С свел это к 2 стандартным методам:

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

Несколько нестандартных возвратили память, которую вам пришлось явно освободить

  • strdup () приходит на ум.
    Общее расширение, но на самом деле не в стандарте.
1 голос
/ 12 ноября 2008

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

int get_string (const char * buffer);

Затем заполните буфер. Но возвращение точки к ошибочным данным - это нормально.

1 голос
/ 12 ноября 2008

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

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

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

0 голосов
/ 04 июня 2014

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

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

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

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

0 голосов
/ 03 июня 2014

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

Если размер заранее неизвестен, рассмотрите возможность передачи функции обратного вызова вашей функции, которая получает 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 голосов
/ 12 ноября 2008

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

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 () позволит избежать случайной перезаписи буфера.

0 голосов
/ 12 ноября 2008

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

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

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

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

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

void GetString(char *out, int maxlen);

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

0 голосов
/ 12 ноября 2008

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

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

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

CreateString()

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

GetString()

когда это не проблема.

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

0 голосов
/ 12 ноября 2008

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

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