Intereting Posts
Указатель на функцию в ПЗУ как генерировать предупреждение о ненулевом аргументе для моей пользовательской функции? Как протестировать ввод является нормальным Является ли & * NULL корректным в C? Лучший способ создания вспомогательного массива из массива в C Доступ к 2D-массиву с использованием двойного указателя на язык функции C OS X: Генерировать дамп ядра без снижения процесса? В C и Objective-C следует использовать 0.5f или 0.5? Биты в C, как мне получить доступ к базовым битам в C float? C / UNIX считывается с ввода (ограничено количеством символов и таймаутом) Почему нам нужно указывать указатель void на int или что-то еще, прежде чем печатать значение в памяти, адрес которой находится в указателе? Преобразование строки двоичного формата в int, в C направление (рисование) в 3d сетке Как вы объединяете строки в C? Использование барьеров памяти для принудительного выполнения заказа

Почему C никогда не реализовал «расширение стека»?

Почему C никогда не реализует «расширение стека», чтобы разрешить (динамически размерные) стековые переменные функции вызываемого абонента ссылаться на вызывающего?

Это может работать, расширяя фрейм стека вызывающего объекта, чтобы включить «динамически возвращенные» переменные из фрейма стека вызываемого. (Вы могли бы, но не должны, реализовывать это с помощью alloca от вызывающего – это может не выдержать оптимизацию.)

Например, если я хотел бы вернуть строку динамического размера «e», реализация может быть:

 --+---+-----+ | a | b | --+---+-----+ callee(d); --+---+-----+---------+---+ | a | b | junk | d | --+---+-----+---------+---+ char e[calculated_size]; --+---+-----+---------+---+---------+ | a | b | junk | d | e | --+---+-----+---------+---+---------+ dynamic_return e; --+---+-----+-------------+---------+ | a | b | waste | e | --+---+-----+-------------+---------+ 

(«Junk» содержит обратный адрес и другие системные метаданные, которые невидимы для программы).

Когда это будет использовано, это приведет к удалению небольшого пространства для стека.

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

Очевидно, нет смысла добавлять такую ​​особенность C на этом этапе своей жизни, меня просто интересует, почему это было не очень хорошо.

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

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

Некоторые возражения против вашей идеи. Некоторые уже упоминались в комментариях. Некоторые приходят с моей головы.

  • C не имеет стеков или кадров стека. C просто определяет области действия и время их жизни, и остается, что реализовать стандарт. Стеки и стековые фреймы – это действительно самый популярный способ реализации семантики C.

  • C не имеет строк. C не имеет массивов как таковых. Ну, у него есть массивы, но как только вы укажете массив в выражении (например, возвращаемое выражение), массив распадается на указатель на его первый элемент. Возrotation «строки» или массива в стек будет иметь существенное влияние на хорошо зарекомендовавшие себя области языка.

  • C имеет structs . Однако вы уже можете вернуть struct . Я не могу сказать вам, как это делается, потому что это деталь реализации.

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

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

Вы должны понимать, что реализация стека сильно продиктована процессором и kernelм ОС. На этом языке не так много говорят. Ограничения, например:

  • Команда ret архитектуры X86 ожидает адрес возврата в ячейке памяти, хранящейся в указателе стека . Таким образом, на вершине не может быть ничего другого (семантическая вершина – обычно это самый низкий адрес, так как стеки имеют тенденцию к росту). Разумеется, вы можете обойти это, но это, вероятно, потребует дополнительных накладных расходов, которые программисты C не собираются платить.

  • Указатель стека определяет, какая часть выделенной памяти стека фактически используется. Когда stream управления изменяется асинхронно (аппаратное прерывание), регистры текущего ЦП, как правило, немедленно сохраняются в адресах памяти ниже указателя стека обработчиком прерываний. Это может произойти в любое время, даже на протяжении большей части кода ядра. Любые данные, хранящиеся ниже того места, где указатель стека указывает на это, будут сбиты. (Ну, технически, это не совсем правильно, обычно есть «красная зона» ниже указателя стека, на который обработчики прерываний не могут писать какие-либо данные, но здесь мы очень сильно вписываемся в особенности архитектурного дизайна.)

  • Уничтожение кадра стека, как правило, представляет собой однократное добавление константы к указателю стека. Это самый быстрый вид команды, которую вы можете получить, как правило, не требуется выполнение одного цикла (он будет выполняться параллельно с некоторым доступом к памяти). Если стек стека имеет динамический размер, фрейм стека должен быть уничтожен путем загрузки указателя стека из памяти, и для этого должен быть сохранен базовый указатель. Это доступ к памяти со значительной задержкой и другой регистр, который необходимо сохранить для использования. Опять же, это накладные расходы, которые обычно не нужны.

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