Почему возвращаемая переменная указателя стека в функции, разрешенной в C? - PullRequest
4 голосов
/ 09 апреля 2019

Я прочитал следующий пост:

Возвращает ли выделенный в куче указатель из функции OK?

Что показывает, что указатель, указывающий на выделенную кучу переменную, в порядке. Тем не менее, технически ли указатель является «переменной, выделенной из стека», которая затем будет освобождена при возврате функции?

Например:

int* test(){
  int arr[5];
  int *ptr = arr;

  return ptr; //deallocated ptr?
}

int *test2(){
  int arr[5];

  return arr;
}

В тесте

Также правильно сказать, что arr - это указатель , который указывает на некий вновь созданный массив int arr, указывающий на &arr[0]. Если arr не является указателем, почему допустимо возвращать его, удовлетворяя типу возвращаемого функцией?

Поскольку как ptr, так и arr предположительно выделены в стеке, почему код работает только в test(), а не test2()? Выдает ли test () неопределенное поведение?

Ответы [ 5 ]

5 голосов
/ 09 апреля 2019

Они оба будут неопределенным поведением , если получен доступ к возвращаемому значению. Таким образом, ни один из них не «в порядке».

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

Цитата C11, глава §6.2.4 / P2, относительно срока службы ( упорная мина )

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

Затем из P5

Объект, идентификатор которого объявлен без связи и без класса хранения статический спецификатор имеет длительность автоматического хранения, [...]

и

Для такого объекта, у которого нет типа массива переменной длины, его время жизни увеличивается от входа в блок, с которым он связан, до тех пор, пока выполнение этого блока не закончится в тем не мение. [...]

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

О, и в стандарте C нет "стека" или "кучи". Все, что у нас есть, - это время жизни переменной.

2 голосов
/ 09 апреля 2019

Оба test и test2() эквивалентны. Они возвращают определенный указатель реализации, который вы не должны разыменовывать, иначе UB следует.

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

1 голос
/ 09 апреля 2019

При входе в функцию в стек добавляется новый кадр стека.Во фрейме стека хранятся все auto (не статические переменные, объявленные в функции).Когда мы покидаем функцию, возвращаемое значение помещается в регистр (обычно R0) в CPU, а затем указатель стека уменьшается, чтобы удалить кадр стека.Затем мы возвращаем управление в точку, где мы вызвали функцию, и получаем возвращаемое значение из регистра.

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

Указатель по-прежнему указывает на то место в памяти, где ранее был выделен массив.Поэтому при увеличении стека память, на которую указывает arr, будет перезаписана.Изменение данных, на которые указывает возвращаемое значение, может привести к некоторым «захватывающим» вещам, поскольку мы не знаем, когда память используется сейчас.

Пример массива и указателя:

char arr[5];
char * ptr = arr;

В этом случае компилятор знает размер arr и не знает размер ptr, поэтому мы можем выполнить sizeof (arr), и компилятор выполнит вычисление во время компиляции.Когда дело доходит до времени выполнения, они являются эквивалентными значениями в памяти.

0 голосов
/ 09 апреля 2019

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

Важно помнить, что в C передача всех параметров и возвращаемых значений выполняется по значению. Если вы "возвращаете указатель", как в return p;, это точно такой же механизм, как если бы вы " вернул целое число ", как в return i;: значение переменной где-то скопировано и получено вызывающей стороной. В случае i это значение может быть 42; в случае p значение может быть 3735928559 (или, другими словами, 0xdeadbeef). Это значение обозначает место в памяти, где, например, Ваш массив находился до того, как перестал существовать, потому что функция вернулась. Адрес не изменяется, когда вы копируете его больше чем на 42 изменения, и полностью не зависит от времени жизни переменной p, которая когда-то содержала его & mdash; в конце концов, оно было скопировано из него как раз вовремя. 1


1 Это выходит за рамки вопроса, но технически , для возвращаемого значения создается временный объект. Время жизни и семантика временных категорий более систематизированы в современном C ++.

0 голосов
/ 09 апреля 2019

Оба случая технически одинаковы.

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

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

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