Я столкнулся с некоторым унаследованным кодом, который содержит функцию как это:
LPCTSTR returnString()
{
char buffer[50000];
LPCTSTR t;
/*Some code here that copies some string into buffer*/
t = buffer;
return t;
}
Теперь, я сильно подозреваю, что это неправильно. Я пытался вызвать функцию, и она действительно возвращает строку, которую Вы ожидаете, что это возвратит. Однако я действительно не вижу, как это происходит: не char
массив, который, как предполагают, был сохранен на стеке и таким образом освобожденным после функциональных выходов? Если я неправ, и это хранится на "куче", разве эта функция не создает утечку памяти?
Ваш код демонстрирует неопределенное поведение - в данном случае UB показывает, что он "работает". Если вы хотите, чтобы массив хранился в куче, вам нужно выделить его с помощью new []. Затем вызывающая функция будет нести ответственность за ее удаление с помощью указателя, возвращаемого функцией.
Ваше мнение верно; код очень неправильный.
Память действительно находится в стеке и уходит вместе с функцией end.
Если бы буфер был статическим , то можно было бы хотя бы ожидать, что он будет работать для одного вызова за раз (в однопоточном приложении).
Это опасная ошибка, скрывающаяся в вашем коде. В C и C ++ вам не разрешено возвращать указатель на данные стека в функции. Это приводит к неопределенному поведению. Я объясню почему.
Программа на C / C ++ работает, помещая данные в программный стек и из него. Когда вы вызываете функцию, все параметры помещаются в стек, а затем все локальные переменные также помещаются в стек. По мере выполнения программы она может вставлять и выталкивать больше элементов в стек и из него в вашей функции. В вашем примере буфер помещается в стек, а затем t помещается в стек .Стек может выглядеть так:
В этот момент t находится в стеке , и он указывает на буфер , который также находится в стеке. Когда функция возвращается, среда выполнения выталкивает все переменные в стеке, что включает t, буфер и параметры. В вашем случае вы возвращаете указатель t, таким образом делая его копию в вызывающей функции.
Если вызывающая функция затем смотрит на то, что t указывает на , она обнаружит, что указывает на память в стеке, которая может существовать, а может и не существовать. (Среда выполнения вытащила его из стека, но данные в стеке могут все еще присутствовать по совпадению, а может и нет).
Хорошая новость в том, что это не безнадежно. Существуют автоматизированные инструменты, которые могут искать подобные ошибки в вашем программном обеспечении и сообщать о них. Они называются инструментами статического анализа. Sentry - один из таких примеров программы, которая может сообщать об этом типе дефекта.
Я согласен с тем, что, скорее всего, здесь происходит неправильный возврат адреса автоматической переменной, однако я повторяю KevenK, что это не гарантируется если это C ++, как указывает тег. Мы не знаем, что такое LPCTSTR. Что, если включенный заголовок содержит что-то вроде этого:
(да, я знаю эти утечки, а не суть)
class LPCTSTR{
private:
char * foo;
public:
LPCTSTR & operator=(char * in){
foo = strdup(in);
}
const char * getFoo(){
return foo;
}
};
Хотя все думают, что поведение не определено, и в данном случае оно кажется правдой, в подобных случаях важно рассмотреть другие возможности.
Например, перегруженный operator = (const char *)
может негласно выделять память требований. Хотя это не так (насколько мне известно) с определениями типов Microsoft, в подобных случаях важно помнить об этом.
В данном случае, однако, кажется удобным, что он работает, и это, конечно, не является гарантированным поведением. Как отмечали другие, это действительно нужно исправить.
В C / C ++ существует очень небольшая разница между массивом и указателем. Итак, утверждение:
t = buffer;
На самом деле работает, потому что «буфер» означает адрес массива. Адрес не сохраняется в памяти явно, пока вы не поместите его в t (т.е. буфер не является указателем). buffer [n] и t [n] будут ссылаться на один и тот же элемент массива. Однако ваш массив выделяется в стеке, поэтому память освобождается, а не очищается, когда функция возвращается. Если вы посмотрите на него, прежде чем он будет перезаписан чем-то еще, он будет выглядеть нормально.
Вы правы, код неправильный. :)
Вам следует изучить распределение памяти в C/C++. Данные могут находиться в двух областях: стеке и куче. Локальные переменные хранятся в стеке. malloc
ed и new
ed данные хранятся в куче. Состояние стека локально для функции - переменные живут во фрейме стека - контейнере, который будет освобожден при возвращении функции. Поэтому указатели становятся нерабочими.
Куча является глобальной, поэтому все данные хранятся в ней до тех пор, пока программист явно не удалит
ed или освободит
d. Вы можете полагаться на эту область.
Вы правы, это не гарантирует работы; и на самом деле, если вы действительно храните строку из 50 000 символов в этом буфере, а затем вызываете некоторую функцию (которая вызывает функцию, которая вызывает функцию ...) после этого, почти в каждой системе эта строка будет повреждена из-за того, что функция стек-фрейм помещается в стек.
Единственная причина, по которой это работает, заключается в том, что устаревшая стековая память (в большинстве систем) не очищается после возврата из функции.
[Edit] Чтобы уточнить, похоже, он работает, потому что у стека не было возможности вырасти на 50 000 байт. Попробуйте изменить его на char buffer [10];
и вызвать некоторые функции после returnString ()
, и вы увидите, что он поврежден.
этот код возвращает указатель на память, выделенную в стеке. это очень опасно, потому что если вы попытаетесь передать этот указатель другой функции, память будет перезаписана вторым вызовом функции.
вместо этого вы можете использовать статический буфер:
static char buffer[50000];
, который не размещен в стеке, поэтому указатель на него остается действительным. (очевидно, это не потокобезопасный).
Утечки памяти нет, но функция по-прежнему неверна - буфер
действительно создается в стеке, и тот факт, что вызывающий может его прочитать, - просто удача (он доступен только сразу после вызов функции returnString ()
.) Этот буфер может быть перезаписан любыми дальнейшими вызовами функций или другими манипуляциями со стеком.
Правильный способ передачи данных вверх по цепочке вызовов - это предоставить функции для заполнения буфер и размер .