Не этот унаследованный код, который возвращает локальный массив символов неправильно?

Я столкнулся с некоторым унаследованным кодом, который содержит функцию как это:

LPCTSTR returnString()
{
    char buffer[50000];
    LPCTSTR t;

    /*Some code here that copies some string into buffer*/

    t = buffer;
    return t; 
}

Теперь, я сильно подозреваю, что это неправильно. Я пытался вызвать функцию, и она действительно возвращает строку, которую Вы ожидаете, что это возвратит. Однако я действительно не вижу, как это происходит: не char массив, который, как предполагают, был сохранен на стеке и таким образом освобожденным после функциональных выходов? Если я неправ, и это хранится на "куче", разве эта функция не создает утечку памяти?

12
задан Keith Pinson 12 February 2013 в 23:52
поделиться

10 ответов

Ваш код демонстрирует неопределенное поведение - в данном случае UB показывает, что он "работает". Если вы хотите, чтобы массив хранился в куче, вам нужно выделить его с помощью new []. Затем вызывающая функция будет нести ответственность за ее удаление с помощью указателя, возвращаемого функцией.

8
ответ дан 2 December 2019 в 05:15
поделиться

Ваше мнение верно; код очень неправильный.

Память действительно находится в стеке и уходит вместе с функцией end.

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

3
ответ дан 2 December 2019 в 05:15
поделиться

Это опасная ошибка, скрывающаяся в вашем коде. В C и C ++ вам не разрешено возвращать указатель на данные стека в функции. Это приводит к неопределенному поведению. Я объясню почему.

Программа на C / C ++ работает, помещая данные в программный стек и из него. Когда вы вызываете функцию, все параметры помещаются в стек, а затем все локальные переменные также помещаются в стек. По мере выполнения программы она может вставлять и выталкивать больше элементов в стек и из него в вашей функции. В вашем примере буфер помещается в стек, а затем t помещается в стек .Стек может выглядеть так:

  • Предыдущий стек
  • Параметры
  • (другие данные)
  • буфер (50000 байт)
  • t (указатель размера)

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

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

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

1
ответ дан 2 December 2019 в 05:15
поделиться

Я согласен с тем, что, скорее всего, здесь происходит неправильный возврат адреса автоматической переменной, однако я повторяю KevenK, что это не гарантируется если это C ++, как указывает тег. Мы не знаем, что такое LPCTSTR. Что, если включенный заголовок содержит что-то вроде этого:

(да, я знаю эти утечки, а не суть)


class LPCTSTR{
private:
  char * foo;

public:
  LPCTSTR & operator=(char * in){
    foo = strdup(in);
  }

  const char * getFoo(){
    return foo;
  }


};

1
ответ дан 2 December 2019 в 05:15
поделиться

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

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

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

1
ответ дан 2 December 2019 в 05:15
поделиться

В C / C ++ существует очень небольшая разница между массивом и указателем. Итак, утверждение:

t = buffer;

На самом деле работает, потому что «буфер» означает адрес массива. Адрес не сохраняется в памяти явно, пока вы не поместите его в t (т.е. буфер не является указателем). buffer [n] и t [n] будут ссылаться на один и тот же элемент массива. Однако ваш массив выделяется в стеке, поэтому память освобождается, а не очищается, когда функция возвращается. Если вы посмотрите на него, прежде чем он будет перезаписан чем-то еще, он будет выглядеть нормально.

2
ответ дан 2 December 2019 в 05:15
поделиться

Вы правы, код неправильный. :)

Вам следует изучить распределение памяти в C/C++. Данные могут находиться в двух областях: стеке и куче. Локальные переменные хранятся в стеке. malloced и newed данные хранятся в куче. Состояние стека локально для функции - переменные живут во фрейме стека - контейнере, который будет освобожден при возвращении функции. Поэтому указатели становятся нерабочими.

Куча является глобальной, поэтому все данные хранятся в ней до тех пор, пока программист явно не удалитed или освободитd. Вы можете полагаться на эту область.

1
ответ дан 2 December 2019 в 05:15
поделиться

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

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

[Edit] Чтобы уточнить, похоже, он работает, потому что у стека не было возможности вырасти на 50 000 байт. Попробуйте изменить его на char buffer [10]; и вызвать некоторые функции после returnString () , и вы увидите, что он поврежден.

5
ответ дан 2 December 2019 в 05:15
поделиться

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

вместо этого вы можете использовать статический буфер:

static char buffer[50000];

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

1
ответ дан 2 December 2019 в 05:15
поделиться

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

Правильный способ передачи данных вверх по цепочке вызовов - это предоставить функции для заполнения буфер и размер .

6
ответ дан 2 December 2019 в 05:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: