Как вернуть строковый литерал из функции

Меня всегда смущает возврат строкового литерала или строки из функции. Мне сказали, что может быть утечка памяти, потому что вы не знаете, когда память будет удалена?

Например, как в приведенном ниже коде реализовать foo(), чтобы вывод кода был «Hello World»?

void foo (       )              // you can add parameters here.
{

}

int main ()
{
    char *c;
    foo (    );
    printf ("%s",c);
    return 0;
}

Кроме того, если возвращаемый тип foo() не void, но вы можете вернуть char*, каким он должен быть?


person skydoor    schedule 20.03.2010    source источник
comment
Ваше требование, чтобы он возвращал char* вместо char const*, фактически вынуждает нас предположить, что простого строкового литерала недостаточно, и поэтому return "hello world"; является недопустимым решением. Каковы ваши требования к постоянству?   -  person Johannes Schaub - litb    schedule 20.03.2010


Ответы (4)


Я предполагаю, что мы не можем изменить main. Чтобы ваша программа работала без утечек, вам нужно что-то, имеющее статическое хранилище:

void foo(char*& pC)  // reference
{
    static char theString[] = "thingadongdong";

    pC = theString;
}

Но на самом деле это не совсем обычный код C++. Вы будете использовать std::string и std::cout, так что вам не нужно беспокоиться о памяти:

std::string foo(void)
{
    return "better thingadongdong";
}

int main(void)
{
    // memory management is done
    std::cout << foo() << std::endl;
}

Если вам интересно, нужно ли что-то освобождать вручную, это делается неправильно.

person GManNickG    schedule 20.03.2010
comment
Не всегда. В конце концов, std::string выполняет выделение вручную. - person Nathan Osman; 20.03.2010
comment
Эх, когда вы не в режиме написания библиотеки, я должен сказать. :) - person GManNickG; 20.03.2010
comment
Ну вот. Намного лучше. Точно мое мнение. - person Nathan Osman; 20.03.2010
comment
Я не уверен, что не так с использованием char *foo() { return Hello World; }, я что-то пропустил? - person Mike Tunnicliffe; 20.03.2010
comment
Я бы использовал std::string насколько это возможно. Все распределение памяти позаботилось. И если вам нужно форматирование, подобное printf(), попробуйте boost::format вместе с std::cout. - person jpyllman; 20.03.2010
comment
@fd, вы не можете изменить строковый литерал, и он вызывает устаревшее преобразование в '03 и имеет неправильный формат в С++ 0x. char* указывает, что OP хочет изменить возвращаемую строку. Мы не можем быть уверены. - person Johannes Schaub - litb; 20.03.2010
comment
@Johannes Йоханнес, ах, как я вижу, описание вопроса, похоже, не указывает на необходимость изменяемой строки. Я отмечаю ваш совершенно правильный комментарий о константности вопроса, но мне интересно, не предполагал ли ОП это ограничение. Казалось, что вопрос больше связан с беспокойством об утечке памяти из-за возврата литерала, я подозреваю, что информативный ответ состоит в том, чтобы объяснить разницу между возвратом литерала (с объявлением const) и возвратом изменяемой строки, длина и/или продолжительность жизни которой должны быть удалось. - person Mike Tunnicliffe; 20.03.2010
comment
Говоря об обычном коде C++, void в пустом списке параметров является избыточным. - person Paul Manta; 16.06.2012
comment
Не то чтобы я хотел бы оптимизировать до того, как это понадобится, но копирует ли ваш обычный пример C++ фактические символы или возвращает адрес литералу статической строки? Чтобы избежать копирования, static std::string my_string = "better thingadongdong"; return my_string с типом возврата const std::string& должно быть хорошо, верно? - person Gauthier; 03.06.2016

Поскольку старое использование char* устарело, вы не можете просто использовать строку?

const char* func1 () {return "string literal";}

string func2 () {return "another string literal";}

Оба они работают нормально, без предупреждений компилятора.

Однако

char* func3 () {return "yet another string literal";}

вообще не компилируется. И не будет

char* func4 () {return &"a ref to a string literal?";}

Страуструп говорит в «Языке программирования C++» (третье издание):

«Строковый литерал размещается статически, поэтому безопасно возвращать его из функции.

const char* error_message (int i)`
{
//...
return "range error";
}

Память, содержащая ошибку диапазона, не исчезнет после вызова error_messages()."

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

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

В любом случае утечек памяти нет: каждый строковый литерал получает свой собственный кусок памяти, который очищается при завершении программы; return to const char* возвращает указатель на участок памяти литерала (зная, что вы не можете его изменить); и возврат к строке делает копию в строковый объект, существующий в коде вызывающей стороны, который очищается вызывающей стороной.

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

person RMS    schedule 16.06.2012

Меня всегда смущает возврат строкового литерала или строки из функции.

Неизменяемая буквенная строка

Насколько я понимаю, вы можете безопасно возвращать строковый литерал напрямую, если тип возвращаемого значения объявлен const, чтобы объявить, что строка не предназначена для изменения. Это означает, что вам не нужно беспокоиться о продолжительности жизни строк/утечек памяти.

Изменяемая нелитеральная строка

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

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

Другой вариант — передать некоторую память функции, которая была выделена вызывающей стороной, и заставить функцию поместить строку в эту память. В этом случае вызывающий объект и выделяет, и несет ответственность за освобождение этой памяти.

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

person Mike Tunnicliffe    schedule 20.03.2010

Что-то вроде этого:

void foo(char ** pChar)
{
    // Make sure the string is shorter
    // than the buffer
    *pChar = new char[256];
    strcpy(*pChar,"Hello World!");
}

Затем назовите это так:

foo(&c);

Как упоминалось в комментарии, будьте осторожны, сохраняемая строка меньше буфера, иначе вы получите... переполнение стека! (каламбур)

person Nathan Osman    schedule 20.03.2010
comment
Вы не можете разыменовать 0 Это приведет к сбою вашей программы. - person Nathan Osman; 20.03.2010
comment
Поскольку выделение выполняется в foo, когда foo возвращается, выделенная память будет висеть. (И не будет никакого способа освободить его.) - person Nathan Osman; 20.03.2010
comment
@ Джордж: В том-то и дело. Не берите указатель, берите ссылку. Это С++. - person GManNickG; 20.03.2010
comment
@GMan это все равно, что сказать, а если я сделаю строку (0)? или и если я сделаю printf(0)?. Он запрещает нулевые указатели в качестве предварительного условия, поэтому передача 0 не разрешена. - person Johannes Schaub - litb; 20.03.2010
comment
@litb: Но ссылка требует этого, а не просто говорит не делать этого. - person GManNickG; 20.03.2010
comment
@GMan а, понятно. Да, ссылки кажутся лучшим инструментом здесь. - person Johannes Schaub - litb; 20.03.2010
comment
зачем применять двойной указатель? В этом случае вы можете просто сделать char* foo() { char* tmp = new char[sizeof("hello world")]; strcpy(tmp, "hello world"); }, если вы действительно хотите сделать это таким образом. - person Shipof123; 27.12.2020