Может ли вызываемый объект напрямую обращаться к переменной стека в вызывающем объекте без указателей в теории?

C является передачей по значению, что означает, что все параметры копируются в кадр стека каждый раз, когда вы вызываете функцию. C также не поддерживает внутренние функции, которые могут получать доступ к локальным переменным или изменять их в лексически объемлющей функции, хотя GNU C делает это как расширение. Единственный способ «передать по ссылке» в C — это передать указатель. Хотя в C++ есть ссылочные типы, это просто указатели за кулисами.

Я подумал о следующем сценарии на гипотетическом низкоуровневом C-подобном языке. Внутри другой функции может быть объявлена ​​функция, видимая только внешней функции и вызываемая только внешней функцией. Если я правильно понимаю, указатель стека увеличивается на фиксированную величину, когда вызывается внутренняя функция, не предполагающая никаких забавных действий, таких как объекты переменного размера. Следовательно, по ходу мыслительного процесса любые локальные переменные, объявленные во внешней функции, будут иметь фиксированные смещения относительно указателя стека даже внутри внутренней функции. Теоретически к ним можно получить доступ так же, как и к другим локальным переменным, без необходимости передачи указателя. И, конечно же, попытка доступа и вызова внутренней функции извне внешней функции приведет к попытке доступа к локальным переменным вне времени жизни во внешней функции и приведет к неопределенному поведению.

Вот иллюстрация: (опять же это не C, а воображаемый C-подобный язык)

void (*address_to_inner)(void);
void outer(void) {
    int a = 10;
    void inner(void) {
        printf("%d\n", a);
    }
    inner(); // Prints 10
    address_to_inner = inner; // Not undefined behavior yet but not good
}
void another(void) {
    outer(); // Prints 10
    address_to_inner(); // Undefined behavior because inner() tries to access `a` after it was deallocated
}

Применим ли мой мыслительный процесс к типичным архитектурам, таким как x86 или ARM? Некоторые функции, такие как автоматическое управление памятью, не поддерживаются в низкоуровневых языках, таких как C, потому что они изначально не поддерживаются архитектурой и требуют слишком много сложной внутренней работы, чтобы быть осуществимыми для низкоуровневого языка. Но является ли мой сценарий «выполнимым» для языка низкого уровня или существуют ли какие-либо архитектурные или технические ограничения, которые я не учел, которые сделали бы реализацию этого нетривиальной в зависимости от того, как работают стековые/локальные переменные и вызовы функций? Если нет, то почему?

Хм... по крайней мере, в случае закрытия JS внутренние переменные больше не доступны при возврате. Вы случайно не видели этот вопрос?

General Grievance 18.04.2023 20:25

@GeneralGrievance Интересно. Языки, с которыми я знаком, такие как Objective-C или Swift, похоже, обрабатывают замыкания так, как я описал в комментарии. Что произойдет в JS, если я верну замыкание, которое обращается к локальной переменной, и вызываю ее? Обновлено: кажется, что в JS он работает так, как задумано.

user16217248 19.04.2023 02:03

@GeneralGrievance Я имею в виду, что это что-то вроде закрытия, но не совсем.

user16217248 19.04.2023 05:25

Вложенные функции GNU C становятся интересными, когда вы берете адрес функции и передаете этот указатель на функцию чему-то другому. Затем он должен сделать трамплин из машинного кода (в стеке) и передать указатель на него, который установит статическую цепочку указателей на вложенную функцию, которая может найти кадр стека своего родителя, даже если он не является его дочерним элементом. См. Реализация вложенных функций. Когда он вызывается из своего родителя, IIRC просто встраивается, даже с отключенной оптимизацией.

Peter Cordes 19.04.2023 06:18

@PeterCordes Итак, GNU C доходит до того, чтобы заставить его работать, а не вести себя неопределенно.

user16217248 19.04.2023 17:00

Локальные переменные родительской функции должны оставаться активными во время вызова вложенной функции через указатель. Указатель функции перестает быть действительным, когда родительская функция возвращается. Таким образом, вызывающий объект по-прежнему должен быть дочерним элементом родителя в дереве вызовов, просто он не обязательно должен быть прямым дочерним элементом. Ваш аргумент о времени жизни var верен, и GCC ничего не делает, чтобы обойти это.

Peter Cordes 19.04.2023 17:21

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

user16217248 19.04.2023 17:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

Другая возможность, подходящая для машины с большим количеством регистров ЦП (x64, RISC V, ARM), может состоять в том, чтобы выполнить распределение регистров локальных переменных как внутри, так и между функциями и их вложенными функциями. Таким образом, как локальные переменные могут находиться в регистрах, так и нелокальные переменные тоже могут находиться в регистрах. Это также предотвратило бы рекурсию, но изменило бы производительность.

Наконец, давайте упомянем встраивание, поскольку оно обеспечивает большую эффективность, которую мы ожидаем от машин с большим количеством регистров ЦП, избегая использования памяти для локальных переменных — когда две функции объединяются путем встраивания, они совместно используют одно и то же пространство для хранения локальных переменных; встраивание может даже удалить пару адресов, взятых вызывающей стороной, а затем разыменовать ее внутри вызываемой, что позволяет сопоставить такую ​​локальную переменную с регистром, а не с памятью, как это было бы необходимо, если бы адрес действительно должен был материализоваться. Поскольку встраивание выполняется путем анализа функций компилятором, оно гибко позволяет одной функции вызывать другую, включая рекурсию, хотя некоторые конструкции могут отключать некоторую оптимизацию.

Другие вопросы по теме