2017-01-25 1 views
3

По данным «Хранение на короткий срок», глава 8 в «Assembly Language Шаг за шагом» (третье издание):Должен ли я использовать стек для хранения в течение длительного времени?

стек следует рассматривать как место, чтобы спрятать вещи на короткий срок. Элементы, хранящиеся в стеке, не имеют имен и вообще должны быть сняты со стека в порядке, в котором они были введены. Последнее, сначала, помните. LIFO!

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

альтернативы, которые я могу думать о том, являются:

  • Heap, но это медленно.
  • Статические переменные, но это продлится всю жизнь программы, что может привести к большому количеству памяти.
+1

, 7 секунд - это вечность данных. – danny117

+1

Ваша книга о языке ассемблера, а не C –

+0

@ M.M Я понимаю это, но в качестве примера я использовал только общий способ работы компиляторов C. –

ответ

1

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

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

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

Глобалы и статические локальные сети, которые я предпочитаю вызывать локальные глобальные переменные, находятся в .data не в стеке. Существуют программисты, которые будут создавать переменные или структуры на уровне main(), которые передаются вниз через каждый уровень вложенности, затрачивая потребление на передачу параметров, которые могли бы быть использованы более эффективно, если это соглашение о тяжелых вызовах стека, даже с передайте по ссылке, вы все еще горите указатель на каждом уровне, где статический глобальный был бы намного дешевле, локальный глобальный все равно стоил бы вам столько же, сколько нестационарный локальный на этом верхнем уровне. вы не можете просто заявить, что глобальные или статические местные жители будут стоить вам больше, я бы сказал, что они намного меньше потребления, зависит от ваших привычек программирования и выбора переменных, если вы создаете новую переменную с новым именем для каждой возможной вещи, Попасть в беду. Но, например, если вы хотите работать с микроконтроллером или работать с другими встроенными функциями, где вы крайне ограничены ресурсами, например, использование только глобалов дает вам гораздо больше шансов на успех, использование вашей памяти почти исправлено, у вас все еще есть память для возврата адрес для функций, которые вложены и не входят в состав. это немного экстремально, с практикой вы можете использовать местных жителей, у которых есть неплохие шансы быть оптимизированными в регистрах, а не использовать стек. Это очень программист, процессор и компилятор, зависящий от того, потребляет ли тяжелое локальное использование или тяжелое глобальное использование меньше памяти. тяжелое местное использование имеет потенциал только для временного использования, но для ограниченных систем анализ, необходимый для обеспечения того, чтобы вы не разбивали стек в программу, или куча требует гораздо больше работы для обеспечения безопасности, каждая строка кода, которую вы добавляете или удаляете, может оказывают существенное влияние на использование стека при сильных локальных переменных. Любая схема обнаружения использования стека мгновенно обойдется вам в большом количестве ресурсов, сжигающих больше этого пространства без добавления какого-либо нового высокоуровневого кода приложения.

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

Так возьмите это, например, код, который написан, чтобы заставить компилятор использовать стек:

unsigned int more_fun (unsigned int); 
unsigned int fun (unsigned int a) 
{ 
    return(more_fun(a)+a+5); 
} 

создание

00000000 <fun>: 
    0: e92d4010 push {r4, lr} 
    4: e1a04000 mov r4, r0 
    8: ebfffffe bl 0 <more_fun> 
    c: e2844005 add r4, r4, #5 
    10: e0840000 add r0, r4, r0 
    14: e8bd4010 pop {r4, lr} 
    18: e12fff1e bx lr 

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

Хотя это нарушает текущую конвенцию для этих набора команд вы могли бы вместо того, чтобы

push {lr} 
push {r0} 
bl more_fun 
add r0,r0,#5 
pop {r1} 
add r0,r0,r1 
pop {lr} 
bx lr 

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

unsigned int more_fun (unsigned int); 
unsigned int fun (unsigned int a) 
{ 
    return(more_fun(a)+5); 
} 

производства 0: e92d4010 толчок {r4, Lr} 4: ebfffffe бл 0 8 : e8bd4010 поп {r4 Л.Р.} с: e2800005 добавить r0, r0, # 5 10: e12fff1e Ьх Л.Р.

и тогда вы сказать мне, но это было сделано. Хорошо отчасти вызывается конвенция, а отчасти потому, что если шина имеет ширину 64 бит, что часто для ARM сейчас, или даже если нет, вы добавляете один такт к транзакции, которая занимает от нескольких сотен часов для этого дополнительного регистра, не большая стоимость, если 64 бита в ширину, а одно нажатие на регистр и поп действительно стоят вам, вы не спасете вас, также оставаясь выровненным на границе 64 бит, когда у вас шина шириной 64 бит, также значительно сэкономит вам. Компилятор в этом случае выбрал r4, r4 не сохраняется здесь. Это просто какой-то регистр, который компилятор решил сохранить в стеке, как вы видите в других связанных с этим вопросах stackoverflow, иногда компилятор использует r3 или другие регистры в этом случай он выбрал r4.

Но помимо этого выравнивания и согласования стека (я мог бы выкопать старый компилятор, чтобы показать, что r4 не существует только lr). Этот код не требовал сохранения входного параметра для выполнения математики после вызова вложенной функции, после того как он перейдет в more_fun(), переменную a можно отбросить.

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

push {r0} 
ldr r0,[r2] 
ldr r1,[r0] 
pop {r0} 

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

push {r0} 
add r0,r1,r2 
str r0,[r3] 
pop {r0} 

С составленным языками стек использовать против некоторых альтернативный первый прочь начинается с дизайна процессора, является набор инструкций голодали регистров общего назначения, использует ли набор инструкций для создания инструкций вызова функций и команд возврата, прерываний и прерываний, или использует регистр, и позволяет вам выбирать, нужно ли сохранять его в стеке. Набор инструкций заставляет вас использовать стеки в основном или это вариант. Следующие привычки программирования будут преподаваться или разрабатываться сами по себе, могут привести к тяжелому или более легкому использованию стека, слишком большому числу функций, слишком большому вложенности, только обратные адреса будут занимать мало байтов в стеке каждого вызова, добавлять сильно локальные переменные , и это может немного пожевать или взорвать его в зависимости от размера функции, количества переменных (переменный размер) и кода в функциях. Если вы не используете оптимизатор, тогда вы получите массивный взрыв стека, у вас не будет падения эффекта скалы добавления еще одной строки к функции, начиная с небольшого использования без стека и большого использования стека, потому что вы нажали регистр использование над этим утесом, одним или несколькими, добавив еще одну строку. неоптимизированное потребление стека тяжелое, но более линейное. Использование регистров - лучший способ уменьшить потребление памяти, но требует много практики при кодировании и просмотра выходных данных компилятора, и надеясь, что следующий компилятор работает одинаково, они часто делают, но иногда они не делают этого. Тем не менее вы можете написать свой код, чтобы быть более консервативным в использовании памяти и все еще выполнять задание. (использование меньших переменных, таких как использование символа вместо int, НЕ обязательно сохраняет вас, для наборов инструкций размера 16, 32 и 64 бит может потребоваться дополнительные инструкции для подписи расширения или маскировки остальной части регистра. набор инструкций и ваш код). А затем есть глобальные переменные, которые почему-то нахмурились, их трудно читать? это глупо. У них есть плюсы и минусы, что вашими потребностями гораздо больше контролируется, минусы да, если вы используете множество переменных, не используйте повторно переменные, которые вы будете потреблять намного больше, и они есть на всю жизнь программы , они не освобождаются, как нестатические локали. статические локали - это просто глобалы с ограниченным объемом, используйте их только тогда, когда вы хотите глобального, но боитесь, чтобы их избегали, или имеют очень специфическую причину, в которой есть короткий список, в основном связанный с рекурсией.

Как медленно куча? Ram - это ram, как правило, если ваша переменная находится в стеке или в куче, он принимает одинаковые нагрузки и магазины, чтобы добраться до нее, кешируют хиты и промахи, хотя вы можете попытаться манипулировать, но все же иногда они иногда ударяются. Некоторые процессоры имеют специальную возможность для чипа для стека, но это не те процессоры общего назначения, которые мы видим сегодня, эти стеки, как правило, довольно малы. Или некоторые встроенные/голые металлические конструкции, вы можете поместить стек на другой RAM, чем data или кучу, потому что вы хотите использовать его и иметь самую быструю память. Но возьмите программу на машине, которую вы читаете, программа, стек и .data/heap, вероятно, являются тем же самым медленным пространством в драме, причем некоторое кэширование пытается сделать это быстрее, но не всегда. «куча», которая является скомпилированной/операционной системой, использует память в любом случае, имеет проблему выделения и освобождения, но после выделения тогда производительность такая же, как .text и .data и стек для многих целевых платформ, мы использовать. используя стек, вы в основном делаете malloc и освобождаетесь с меньшими накладными расходами, чем системный вызов. Но вы все равно можете использовать кучу эффективным способом, подобно тому, как компиляторы использовали стек выше, одну команду, чтобы нажимать и нажимать две вещи, экономя несколько до десятков до сотен тактовых циклов. вы могли бы malloc и освобождать более крупные вещи реже. И люди делают это, когда не имеет смысла использовать стек (из-за размера структуры или массива или массива структур).

+0

Эй, оцените информативный ответ. Не возражаете, если я помогу немного почистить его? –

2

Стек обычно используется для нажатия аргументов на вызов функции, хранения локальных переменных функции и сохранения дорожек адреса возврата (инструкция, в которой он начнет выполнение после возврата из текущей функции). Но как будет реализован вызов функции, зависит от реализации компилятора и calling conventions.

компиляторы используют стек для основном все

Это не так. Компилятор C не ставит глобальные и статические переменные в стеке.

Означает ли это, что стек является наилучшим способом хранения переменных, как краткосрочных, так и долгосрочных?

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

Куча, но это медленно.

Это потому, что для управления им требуется некоторое управление во время выполнения. Если вы хотите выделить кучу в сборке, вам придется самостоятельно управлять кучей. На языках высокого уровня, таких как C, C++, язык исполнения и ОС управляют кучей. У вас этого не будет в сборе.

+0

Кстати, если вы хотите узнать больше о генерации кода сборки из кода c/C++, вы можете найти [gcc explorer] (http://gcc.godbolt.org/) интересным. – army007

Смежные вопросы