Массивы, куча, стек и типы значений

Глобальные и статические переменные

Глобальные и статические переменные, в данном случае, самые малоинтересные. Они существуют в памяти все время работы программы,
поэтому их размещение выполняется компилятором в сегменте данных (для настольных ПК) или просто в оперативной памяти (для
микроконтроллеров). Каждая переменная получает постоянный адрес выделенного ей блока памяти соответствующего размера. Статусы
«глобальная», «внешняя», «статическая» не имеют значения для процессора. Они нужны компилятору, для разграничения доступа к
переменным, и программисту, так как отражают семантику видимости данных.

🔎 Давайте разберем каждый из элементов:

Title (название): название должно быть максимально информативным и соответствовать вашему вопросу настолько, насколько это возможно

Так вы сможете привлечь внимание пользователей, которые, потенциально, могли бы вам помочь.

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

Format (форматирование): это основной бар для всех дополнительных настроек

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

Help (справка): если вы нажмете на каждую категорию, вы увидите синтаксис, используемый для каждого элемента, и советы о том, как их использовать для правильного форматирования описания вопроса.

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

Примечание: Между двумя полосками, идущими ниже поля для описания вопроса, вы увидите предварительный просмотр вашего сообщения, в том виде, в каком оно, в итоге, будет размещено на платформе. Удивительно, правда?

Как правильно задавать вопросы на Stack Overflow

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

Примечание: Подробную информацию и рекомендации можно найти в «Справочном центре» Stack Overflow.

Начните помогать другим пользователям на Stack Overflow

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

Подумайте — ваши ответы, возможно, помогут тысячам разработчиков по всему миру! Удивительно, правда?

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

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

Примечание: Вы также можете нажать на интересующие вас теги. Поиск вопросов будет отфильтрован согласно выбранным тегам.

Начните помогать другим пользователям на Stack Overflow

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

Подумайте — ваши ответы, возможно, помогут тысячам разработчиков по всему миру! Удивительно, правда?

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

Затем вы увидите сам вопрос:

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

Примечание: Вы также можете нажать на интересующие вас теги. Поиск вопросов будет отфильтрован согласно выбранным тегам.

How to deal with the java.lang.stackoverflowerror

  • The simplest solution is to carefully inspect the stack trace and detect the repeating pattern of line numbers. These line numbers indicate the code being recursively called. Once you detect these lines, you must carefully inspect your code and understand why the recursion never terminates.
  • If you have verified that the recursion is implemented correctly, you can increase the stack’s size, in order to allow a larger number of invocations. Depending on the Java Virtual Machine (JVM) installed, the default thread stack size may equal to either , or . You can increase the thread stack size using the flag. This flag can be specified either via the project’s configuration, or via the command line. The format of the argument is:

Работа С Ошибкой StackOverflowError

Лучшее, что можно сделать при обнаружении StackOverflowError , – это осторожно проверить трассировку стека, чтобы определить повторяющийся шаблон номеров строк. Это позволит нам найти код, который имеет проблемную рекурсию

Лучшее, что можно сделать при обнаружении || StackOverflowError||, – это осторожно проверить трассировку стека, чтобы определить повторяющийся шаблон номеров строк. Это позволит нам найти код, который имеет проблемную рекурсию

Эта трассировка стека создается Бесконечной рекурсией С ручным тестом условия завершения , если мы опустим объявление ожидаемого исключения:

java.lang.StackOverflowError

 at c.b.s.InfiniteRecursionWithTerminationCondition
  .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)
 at c.b.s.InfiniteRecursionWithTerminationCondition
  .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)
 at c.b.s.InfiniteRecursionWithTerminationCondition
  .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)
 at c.b.s.InfiniteRecursionWithTerminationCondition
  .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)

Здесь можно увидеть повторение строки № 5. Именно здесь выполняется рекурсивный вызов. Теперь это просто вопрос изучения кода, чтобы увидеть, правильно ли выполняется рекурсия.

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

java.lang.StackOverflowError
  at c.b.s.ClassTwo.(ClassTwo.java:9)
  at c.b.s.ClassOne.(ClassOne.java:9)
  at c.b.s.ClassTwo.(ClassTwo.java:9)
  at c.b.s.ClassOne.(ClassOne.java:9)

Эта трассировка стека показывает номера строк, которые вызывают проблему в двух классах, находящихся в циклической связи. Строка номер 9 класса Два и строка номер 9 класса Один указывают на местоположение внутри конструктора, где он пытается создать экземпляр другого класса.

После тщательной проверки кода и если ни одно из следующих действий (или любая другая логическая ошибка кода) не является причиной ошибки:

  • Неправильно реализованная рекурсия (т. е. без условия завершения)
  • Циклическая зависимость между классами
  • Создание экземпляра класса в том же классе, что и переменная экземпляра этого класса

Было бы неплохо попытаться увеличить размер стека. В зависимости от установленной JVM размер стека по умолчанию может варьироваться.

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

When Should You Use It?

Before we look at what might cause a in Java code, let’s first take a moment to review what a actually is. Most applications are allocated a range of that the application can use during execution. These addresses are stored and used as simple pointers to bytes of data (i.e. memory). This collection of addresses is known as the assigned to the application, and it contains a specific range of that can be safely used by the application.

Unfortunately, at least for the foreseeable future, available memory is a finite resource. A Java application is limited to the bounds of its assigned . Processes like will constantly free up memory that is no longer in use, but, by and large, there is a limited quantity of available to any given application.

When an application attempts to use memory outside of its assigned a error typically occurs. The runtime that is handling the application cannot safely allow said application to use memory that hasn’t been assigned to it, so the only logical course of action is to throw an error of some sort. In the case of Java, this is where the comes in.

There are many different ways a can occur within any given application, but one of the most common (and easily understood) is . This essentially means that a function or method is calling itself, over and over, ad nauseam. Different languages handle infinite recursion differently, but the Java Virtual Machine () handles infinite recursion by eventually throwing a . To illustrate this behavior our example code is quite simple, primarily performed in the class:

As you can see, we have a few private members, along with the method, which attempts a simple task: Iterate the field, then call itself again. We also catch the potential errors (or ) that might come up from this process.

Let’s create a new instance of and start the recursive process by calling the method:

Executing the few lines of code above produces the following output:

We can see that a was thrown, as expected, and it took about iterations before the error occurred, with a total processing time of about milliseconds. This is just one test, so let’s run it a few more times and record the results:

What’s immediately interesting is that, while a is thrown every time, the number of recursive iterations necessary to cause the error changes every time, but within a reasonably similar range. The reason for this difference is due to the vast quantity of different factors within the system when execution occurs. For example, the JVM I’m testing this on is Windows 10 64-bit with 16GB of memory, but if we run this application on other machines (), we might see completely different iteration counts and/or elapsed times.

The Airbrake-Java library provides real-time error monitoring and automatic exception reporting for all your Java-based projects. Tight integration with Airbrake’s state of the art web dashboard ensures that gives you round-the-clock status updates on your application’s health and error rates. easily integrates with all the latest Java frameworks and platforms like , , , , , , , and many more. Plus, allows you to easily customize exception parameters and gives you full, configurable filter capabilities so you only gather the errors that matter most.

Check out all the amazing features Airbrake-Java has to offer and see for yourself why so many of the world’s best engineering teams are using Airbrake to revolutionize their exception handling practices! Try Airbrake free for 30 days.

Локальные переменные

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

Локальные переменные в языке С, если иное не указано, получают модификатор auto, но
сейчас это для нас не важно. Стандартным местом размещения локальных переменных является стек.

Как же быть, если архитектура процессора, например, микроконтроллера, не имеет стека для размещения данных? Так микроконтроллеры
Microchip PIC семейства midrange имеют аппаратный стек всего из 8 ячеек, причем программно он не доступен и автоматически
используется только в командах вызова процедуры, возврата из процедуры, и для прерываний. В таком случае компилятор будет
использовать под стек обычную область памяти, так называемый программно управляемый стек.

Давайте рассмотрим небольшой пример. При этом пока не будем обращать внимания на то, что main это процедура.

            void main(void) {
                int     var1;
                char    var2;
                struct  {
                    int    id;
                    int    val;
                }       var3;
                    .  .  .
            }

Вспомним, что стек размещается в направлении уменьшения адресов. Это не очень принципиально, но обычно бывает именно так.
Сначала в стеке размещается переменная var1, потом var2, затем структура var3. Вот как это будет выглядеть.

Предположим, что тип int занимает два байта. Тогда адрес переменной var3 будет равен содержимому регистра SP
(указатель стека). Переменная var2 расположится по адресу SP+4, а переменная var1 по адресу SP+6

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

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

Теперь немного усложним наш пример добавив вложенные блоки.

            void main(void) {
                int     var1;
                char    var2;
                struct  {
                    int    id;
                    int    val;
                }       var3;
                
                for(int i=0; i<10; i++) {
                    int  tmp;
                    tmp=var2+var3.id;
                    var3.val=tmp;
                }
                    .  .  .
            }

Теперь наш стек будет выглядеть так

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

Оператор for создает новый блок, вложенный в блок main,
который будет для блока for блоком верхнего уровня. Когда выполнение дойдет до закрывающей фигурной скобки оператора for фрейм2 будет удален.

Но теперь стала заметной одна проблема, внутри оператора for нам нужен доступ к переменным var2 и var3 внешнего блока, а указатель
стека у нас изменился и теперь SP указывает на переменную tmp, а не на var3. Вот тут и становится понятной еще одна из функций
стекового фрейма. Переменные в стековых фремах адресуются не относительно SP, а относительно начала фрейма. Если же нужен доступ
к переменной одного из внешних блоков, то к нужно еще добавить смещение до начала соответствующего фрейма. Это напоминает получение
доступа к элементам массива, что мы рассматривали ранее в другой статье. Для нашего рисунка адрес переменной var3 будет равен «начало
фрейма 1» минус 6 независимо от того, появились в стеке новые фреймы, или нет. В процессорах архитектуры x86 есть регистр BP (EBP), который
называется «указатель базы» и который, в отличии от регистров SI, DI и BX (при косвенной адресации), как раз формирует адрес относительно
сегмента SS, то есть стека. Вот этот регистр и используется для хранения адреса начала стекового фрейма, а регистр SP, как обычно,
используется для указания вершины стека. В процессорах другой архитектуры регистра аналогичного BP может не быть, или его функции может
исполнять другой регистр, суть от этого не меняется.

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

Репутация

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

На Stack Overflow существует несколько категорий значков, таких как: значки вопросов, значки ответов, значки участия, значки тегов, значки модерации и многие другие.

Примечание: Полный список значков и их описания можно найти в Stack Overflow Badges Article.

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

Это только начало

Получать новые знания и навыки в сфере IT и программировании — это своеобразное, удивительное путешествие, в котором вы всегда найдете чему поучиться! Я уверена, что вам действительно понравится Stack Overflow.

3 ответа

Лучший ответ

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

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

Некоторые другие проблемы с глобалами:

  1. Если у вас есть какой-либо асинхронный код, который изменяет глобальные переменные, или код, управляемый таймером, который изменяет глобальные переменные, и одновременно могут выполняться несколько асинхронных операций, несколько асинхронных операций могут наступать друг на друга посредством модификации одних и тех же глобальных переменных.
  2. Глобальные переменные часто не объявляются вблизи точки использования, поэтому чтение кода может быть более сложным.
  3. Глобальные переменные обычно не отображаются автоматически в отладчике (как локальные переменные), что делает отладку немного менее удобной.
  4. IE автоматически определяет группу глобальных переменных на основе некоторых имен в DOM, которые могут конфликтовать с вашими глобальными переменными, даже если вы этого не понимаете.
  5. Простое пропускание ключевого слова «var» в локальной переменной делает его глобальной переменной и может привести к путанице в коде, если это имя уже используется в качестве предполагаемой глобальной переменной. Я видел, как это происходило с конструкцией раньше. Может быть трудно отследить, что не так.
  6. Глобальные переменные сохраняются на протяжении всего сценария. Если кто-то использует глобальную переменную для хранения ссылки на объект для чего-то, что не должно существовать в течение всего срока действия сценария, это может привести к тому, что сценарий будет использовать больше памяти, чем в противном случае.
  7. Глобальные переменные в браузере существуют в области видимости объекта , поэтому они могут конфликтовать не только с другими глобальными переменными, но и с чем-либо еще в объекте окна.

7

jfriend00
9 Авг 2011 в 01:51

Вы не должны использовать глобальные переменные в javascript из-за возможных конфликтов с другими скриптами. Например, вы пишете плагин jQuery. Ваши глобальные переменные могут перезаписывать глобальные переменные из другого скрипта.

Таким образом, чтобы свести к минимуму эту возможность перезаписи, вы должны использовать только одну глобальную переменную. Если вы используете jQuery, вы не должны использовать глобальные переменные вообще. Вы можете расширить глобальный объект $. Например:

Если вы используете чистый JavaScript, чем вы должны использовать проблему пространства имен. Создайте только одну глобальную переменную для вашего приложения. Все остальные переменные будут только свойствами этой глобальной переменной. Например:

UPD: помните, что вы должны объявлять переменные с . Если объявленная переменная не будет глобальной. Ошибки, вызванные забыванием ключевого слова , трудно уловить

Larry Cinnabar
9 Авг 2011 в 00:09

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

2

Travis
8 Авг 2011 в 23:48

6 ответов

Целые «ссылочные типы в куче, типы значений в стеке» — это не только плохой способ взглянуть на это, но и неправильный.

Я могу быть несколько полезной абстракцией, чтобы иметь мысленное представление о том, что происходит за кулисами. Но ни то, ни другое не относится ни к одной из версий JIT-компиляторов. Возможно, в этом суть проблемы, фактическое расположение выделений — это деталь реализации JIT-компилятора.

Существует как минимум шесть мест, в которых значение типа значения может жить с джиттерами основного потока (x86 и x64):

  • в кадре стека, помещается туда с помощью объявления локальной переменной или вызова метода
  • в регистре процессора, очень распространенная оптимизация, выполняемая JIT в сборке выпуска. И используется для передачи аргументов в метод, первые два x86, четыре для x64. И локальные переменные, когда это возможно
  • в стеке FPU, используемый джиттером x86 для значений с плавающей запятой
  • в куче GC, когда значение является частью ссылочного типа
  • в куче загрузчика домена приложения, когда переменная объявлена ​​как статическая
  • в локальном хранилище потока, когда переменная имеет атрибут .

Объекты ссылочного типа обычно размещаются в куче GC. Но я знаю одно конкретное исключение: интернированные строки, созданные из литералов в исходном коде, размещаются в куче загрузчиков AppDomain. Он полностью ведет себя как объект во время выполнения, за исключением того, что он не связан с кучей GC, сборщик просто не может его увидеть.

Адресация вашего фрагмента кода:

  • да, скорее всего, «а» будет храниться в куче GG
  • «x» всегда передается в регистр процессора на x86 и x64. «y» будет в регистре процессора на x64, стек на x86.
  • «c», скорее всего, вообще не существует, удалено компилятором JIT, потому что код не имеет никакого эффекта.

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

Места хранения (переменные, поля, элементы массива и т. д.) ссылочных типов содержат ссылки на объекты в куче; места хранения примитивных типов значений хранят свои значения внутри себя; места хранения структурных типов содержат все свои поля, каждое из которых может быть ссылкой или типом значения, внутри себя. Если экземпляр класса содержит две разные ненулевые строки, Point и целое число, координаты X и Y точки, а также отдельное целое число и ссылки на две строки будут храниться в одной куче. объект. Каждая из строк будет храниться в отличном объекте кучи. Ключевым моментом в местах хранения классов и структур является то, что за исключением случая, когда сущность класса содержит ссылку на себя, каждое поле ненулевого ссылочного типа в классе или структуре будет содержать ссылку на некоторый другой объект, который будет в куче.

Думайте об этом в терминах C /C ++.

Каждый раз, когда вы создаете что-то «новое» или используете malloc, который помещается в кучу, то есть «объект» попадает в кучу, сам указатель помещается в стек внутри области видимости структуры ( или функция, которая на самом деле просто еще одна структура), частью которой она является. Если это локальная переменная или ссылочный тип (указатель), она помещается в стек.

Другими словами, объект> <на который указывает ссылочный тип, находится в куче, это просто указатель, который находится в стеке. Утечки памяти происходят, когда программа выталкивает указатель из стека, но память в куче не была освобождена для использования — как узнать, какую память освободить, если ссылка на ее местоположение была потеряна? Что ж, C /C ++ не мог, вы должны были сделать это самостоятельно, прежде чем ссылка была вытолкнута из стека и потеряна навсегда, но именно здесь появляются современные языки со своей причудливой манерой «кучи мусора». По-прежнему предпочтительнее явно очистить любую выделенную кучу памяти, чем неявно, оставив ее для захвата GC, так как это «дешевле» (с точки зрения ресурсов ЦП).

Цитата Джона Скита из его известного блога о том, как и где хранятся ссылочные типы и типы значений. в приложении .Net:

Solutions to StackOverflowError

There are a couple of strategies to address StackOverflowError.

3.1 Fix the Code

Because of a non-terminating recursive call (as shown in the above example), threads stack size can grow to a large size. In that circumstance, you must fix the source code which is causing recursive looping. When ‘StackOverflowError’ is thrown, it will print the stacktrace of the code that it was recursively executing. This code is a good pointer to start debugging and fixing the issue. In the above example it’s ‘’  method.

3.2 Increase Thread Stack Size (-Xss)

There might be a legitimate reason where a threads stack size needs an increment. Maybe thread has to execute a large number of methods or lot of local variables/created in the methods thread has been executing. In such circumstance, you can increase the thread’s stack size using the JVM argument: ‘-Xss’. Pass this argument when you start the application. Example:

-Xss2m

This will set the thread’s stack size to 2 Mb. It might bring a question, what is the default thread’s stack size? Default thread stack size varies based on your operating system, java version & vendor.

JVM version

Thread stack size

  Sparc 32-bit JVM   

512k

  Sparc 64-bit JVM  

1024k

  x86 Solaris/Linux 32-bit JVM

320K

  x86 Solaris/Linux 64-bit JVM

1024K

  Windows 32-bit JVM

320K

  Windows 64-bit JVM

1024K

Переполнение стека

Стек имеет ограниченный размер и, следовательно, может содержать только ограниченный объем информации. В Windows размер стека по умолчанию составляет 1 МБ. На некоторых Unix-машинах он может достигать 8 МБ. Если программа попытается поместить в стек слишком много информации, произойдет переполнение стека. Переполнение стека происходит, когда вся память в стеке была выделена – в этом случае дальнейшие размещения начинают переполняться в другие разделы памяти.

Переполнение стека обычно является результатом выделения слишком большого количества переменных в стеке и/или выполнения слишком большого количества вызовов вложенных функций (где функция A вызывает функцию B, вызывающую функцию C, вызывающую функцию D и т.д.). В современных операционных системах переполнение стека обычно приводит к тому, что ваша ОС выдаст нарушение прав доступа и завершит программу.

Вот пример программы, которая может вызвать переполнение стека. Вы можете запустить его на своей системе и посмотреть, как она завершится со сбоем:

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

В Windows (Visual Studio) эта программа дает следующий результат:

-1073741571 – это c0000005 в шестнадцатеричном формате, что представляет собой код ОС Windows для нарушения прав доступа

Обратите внимание, что «hi» никогда не печатается, потому что программа завершается до этого момента

Вот еще одна программа, которая вызовет переполнение стека, но по другой причине:

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

У стека есть достоинства и недостатки:

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

5 ответов

Лучший ответ

Спросите себя: какая часть ссылочного типа хранится в куче? Что за память? Из чего состоит ссылочный тип?

— В первую очередь, он состоит из памяти его переменных-членов. 1) Это данные, которые хранятся в куче. Итак, в вашем примере это будет переменная .

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

1)

1

Konrad Rudolph
9 Мар 2010 в 09:57

Я буду говорить все вместе с тобой. Язык C #:

Цитирую вас :

TL; DR; Это неправда. Пожалуйста, прочтите подробности, которые я разместил ниже. В вашем случае экземпляр объекта класса и переменная-член будут храниться в куче независимо от того, что является типом значения.

Чтобы резюмировать, как переменные хранятся в куче и стеке: :

Стек всегда используется для хранения следующих двух вещей:

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

В куче хранятся следующие данные:

  • Содержание объектов ссылочного типа.
  • Все, что структурировано внутри объекта ссылочного типа.

Например.

Вот подробности выделения памяти:

  1. obj, который является ссылкой на объект — Стек
  2. Экземпляр Myclass, на который указывает переменная obj — управляемая куча
  3. Тип значения Переменная-член i — управляемая куча
  4. dst, который является ссылкой на объект набора данных — управляемая куча
  5. Экземпляр набора данных, на который указывает переменная dst — управляемая куча
  6. Параметр типа значения j — Стек
  7. dst2, который является ссылкой на объект набора данных — Stack
  8. Экземпляр набора данных, на который указывает переменная dst2 — управляемая куча
  9. Тип значения Локальная переменная k — Стек
  10. dst3, который является ссылкой на объект набора данных — Stack
  11. Экземпляр набора данных, на который указывает dst3 — управляемая куча

Думаю, я рассмотрел все перестановки и комбинации.

Еще раз цитирую вас :

Первое, что я хочу здесь добавить, это то, что объект и переменные не взаимодействуют. Это код (исполняемые инструкции IL, присутствующие в методе), который взаимодействует с общедоступными переменными экземпляра класса (также известного как объект).

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

Поэтому всякий раз, когда выполняется инструкция IL для метода , у нее есть указатель на , который является ссылкой на текущий экземпляр объекта в куче. Ссылка различна для разных экземпляров объекта. всегда указывает на макет начального адреса памяти вашего текущего экземпляра объекта. Оттуда требуется смещение, чтобы добраться до адреса памяти, где находится , чтобы получить к нему доступ / изменить.

Надеюсь, это поможет вам составить ментальную модель того, как код C # работает под капотом в среде CLR.

RBT
7 Ноя 2017 в 07:04

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

РЕДАКТИРОВАТЬ:

Обязательно к прочтению разработчикам .net: Память в .Net и Справочная информация и значения.

Zaki
10 Мар 2010 в 06:48

Как правило, поиск переменных — задача компилятора.

Gabriel Ščerbák
9 Мар 2010 в 09:54

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

4

Brian Rasmussen
9 Мар 2010 в 09:59

Заключение

Остается ответить на последний вопрос, зачем все так сложно? Неужели нельзя просто передавать параметры через переменные
в памяти? Да, так и было когда то. Например, так передавались параметры в Fortran IV. Но такой подход имеет два минуса.
Первый, не самый важный, при таком способе будет больше расход памяти под данные. Ведь в стеке переменные и параметры процедур
могут занимать одно и то же место, если они не нужны одновременно. Второй, и уже существенный минус, будет невозможно реализовать
повторно-входимые (реентерабельные, reentrant) процедуры. То есть, процедуры которые можно вызывать снова до того, как процедура
отработала предыдущий вызов. Это нужно при разработке многопоточных программ и процедур, которые могут быть вызваны и из
основной программы, и из обработчика прерывания. Ну и, следовательно, нельзя реализовывать рекурсивные процедуры.

А вот с микроконтроллерами все сложнее. Их аппаратные ресурсы меньше, часто значительно, чем доступные микропроцессорам
настольных систем и серверов. Да и архитектура бывает отличной от фон Неймановской, например, микроконтроллеры Microchip
PIC имеют Гарвардскую архитектуру, а их оперативная память является, фактически, набором регистров общего назначения. Как
я уже упоминал, в таких случаях используется программная имитация аппаратного стека, но полноценные фреймы не формируются.
И повторная входимость для процедур недоступна, как и рекурсия. Однако, иногда остается необходимость в процедурах, которые
могут вызываться и из основной программы, и из обработчика прерывания. Некоторые компиляторы, например линейки XC, в таком
случае скрыто дублируют код таких процедур, что бы одну копию использовать из основной программы, а вторую из прерывания.
Естественно, дублируется не только код, но и локальные переменные. Однако обсуждение тонкостей передачи процедурам управления,
написания переносимого, позиционно-независимого и повторно-входимого кода выходит далеко за рамки данной статьи и будет обсуждаться
отдельно.

Вот собственно и все. Мы рассмотрели как размещаются в памяти переменные в процессе выполнения программ. В данном случае,
написанных на С. Но примерно так же будет и для программ на Pascal, и других языках. Будут отличаться детали, но сам принцип
останется таким же.

Вы можете обсудить данную статью или задать вопросы автору на форуме

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: