Где хранятся глобальные переменные
Перейти к содержимому

Где хранятся глобальные переменные

  • автор:

Почему глобальные переменные инициализируются нулем, а локальные — мусором по умолчанию?

Почему глобальные и статические переменные инициализируются нулем, а локальные — рандомными значениями по умолчанию?

Отслеживать
218k 15 15 золотых знаков 117 117 серебряных знаков 229 229 бронзовых знаков
задан 3 апр 2021 в 9:36
ТарасПрогер ТарасПрогер
2,279 1 1 золотой знак 6 6 серебряных знаков 27 27 бронзовых знаков

В этом нет никакого «тайного умысла». Просто так сложилось исторически. Если вам нужно, чтобы локальная переменная инициализировалась нулём, вы всегда можете добавить явный инициализатор. Если первое, что вы делаете после объявления переменной — это присваиваете ей какое-нибудь значение, то зачем тратить ресурсы процессора на бесполезную инициализацию? Часть философии языка — «Не платить за то, что не используется». Как-то так.

3 апр 2021 в 9:46

Возможно, вам просто так повезло. Насколько я понимаю ничем они не инициализируются, просто уж как попадёт, что было в этой области памяти до выделения под ваши объекты.

3 апр 2021 в 9:47

@wololo: Расходы на ненужную инициализацию были бы причиной 15 лет назад, но сейчас оптимизаторы поумнее всё-таки: godbolt.org/z/z4bfKKa7f. Так что платить не приходится.

3 апр 2021 в 10:47
Я бы задал вопрос по другому — зачем глобальные переменные вообще чем-то инициализуются?
3 апр 2021 в 10:52

@n1kzzz, один ответ очевиден — Керниган с Ритчи так решили. Почему именно так? Я точно не знаю, но думаю, что инициализация статиков не нулями это практически бесплатно, инициализация локальных стоит лишних усилий. Почему надо что-то делать с неинициализированными статиками? Наверное по аналогии с другими все же чем-то прописать, вот и решили нулями

3 апр 2021 в 11:53

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

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

Отслеживать
ответ дан 3 апр 2021 в 10:01
n 1 k z z z n 1 k z z z
1,481 3 3 серебряных знака 21 21 бронзовый знак

У бинаря во многих ОСях есть несколько различных секций(еще их могут называть сегментами):

  1. Секция .text — для исполняемого кода
  2. Секция .data — для инициализированных данных
  3. Секция .bss — для неинилизированных данных
  4. Секция стека — писать, что это такое, долго, но главное понять, что в основном она используется для реализации возрата управления из функций, для передачи в нее аргументов и для хранения в функции локальных переменных

По итогу, что имеем: Все локальные переменные хранятся на стеке, а так как он может использоваться и для много чего другого, то при выделении памяти в этом месте, он может содержать в себе что угодно(значения локальных переменных, оставшиеся после работы других функций и т.д). Поэтому неинициализированные локальные переменные содержат в себе мусор. Другая ситуация с глобальными и статическими переменными, если они инициализированы, то они хранятся в секции .data. Если они неинициализированы, то тогда компилятор помещает их в секцию .bss — а в ней при старте программы хранятся нули, вот и получаем, что неинициализированные глобальные и статические переменные содержат 0

Глобальные переменные

В противоположность локальным переменным глобальные переменные видны всей программе и могут использоваться любым участком кода. Они хранят свои значения на протяжении всей работы программы. Глобальные переменные создаются путем объявления вне функции. К ним можно получить доступ в любом выражении, независимо от того, в какой функции находится данное выражение.

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

void func1(void) , func2(void);

int count; /* count является глобальной переменной */

int main(void)
count = 100;
func1 ();
return 0; /* сообщение об удачном завершении работы */
>

void func1 (void)
func2 ();
printf(«счетчик %d», count); /* выведет 100 */
>

Рассмотрим поближе данный фрагмент программы. Следует понимать, что хотя ни main(), ни func1() не объявляют переменную count, но они оба могут ее использовать. func2() объявляет локальную переменную count. Когда func2() обращается к count, она обращается только к локальной переменной, а не к глобальной. Надо помнить, что если глобальная и локальная переменные имеют одно и то же имя, все ссылки на имя внутри функции, где объявлена локальная переменная, будут относиться к локальной переменной и не будут иметь никакого влияния на глобальную,. это очень удобно. Если забыть об этом, то может показаться, что программа работает странно, даже если все выглядит корректно.

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

  1. Они используют память в течение всего времени работы программы, а не тогда, когда они необходимы.
  2. Использование глобальных переменных вместо локальных приводит к тому, что функции становятся более частными, поскольку они зависят от переменных, определяемых снаружи.
  3. Использование большого числа глобальных переменных может вызвать ошибки в программе из-за неизвестных и нежелательных эффектов.

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

Два способа написания mul( )

Общий Частный
int mul(int х, int у)
return(x*y);
>
int х, у;
int mui(void)
return(x*y);
>

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

Где хранятся глобальные переменные

Все компиляторы языка программирования генерируют код, который во время выполнения хранится в оперативной памяти. Размещение данных в памяти, чтобы память была зарезервирована в exe-модуле, осуществляется по формуле:

Определение Объявление = инициализация;

int i=1; // тип идентификатор=значение 

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

  • глобальное, когда в любой момент во время работы с переменной всегда ассоциирована область памяти и обратиться к ней можно из любой точки программы;
  • статическое, когда во время работы с переменной ассоциирована область памяти, но обратиться к ней можно только из определенных точек программы;
  • локальное, когда при каждом входе в блок операторов < >для хранения переменной выделяется область памяти, а при выходе память освобождается и переменная теряет свое значение.

Для управления статическим размещением переменных в памяти, их временем жизни и областью видимости язык программирования C++ предоставляет понятие классов памяти и ряд ключевых слов модификаторов типа:

  1. extern используется для определения глобальных переменных. Память для переменных extern распределяется постоянно. Такая переменная глобальна для всех функций и доступна в любой точке программы. Значение переменной всегда сохраняется.
  2. static используется для определения статических переменных. Статические определения используются в случае если необходимо сохранять предыдущее значение при повторном входе в блок операторов < >. Такая переменная инициализируется единственный раз, когда программа встречает ее объявление.
  3. auto используются для определения автоматических переменных. Переменные определенные внутри функции или блока операторов < >по умолчанию являются автоматическими. При входе в блок < >программа автоматически располагает переменную в сегменте стека. При выходе из блока память освобождается, а значения теряются.
  4. register используются для определения регистровых переменных. Переменные с классом памяти register, ассоциируются со скоростными регистрами памяти процессора. Но это не всегда возможно, поэтому часто компилятор преобразует такие переменные к классу auto.

Переменные extern и static явно не инициализированные программистом устанавливаются системой в нуль. Переменные auto и register не инициализируются и могут содержать «мусор».

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

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

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

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

///////////////////////////////////////////////////////////////////////////// // Прикладное программирование // Пример 13. Область действия переменных // // Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru // Университет ИТМО ///////////////////////////////////////////////////////////////////////////// // подключение библиотеки ввода-вывода #include // подключение стандартного пространства имен для использования библиотек using namespace std; // прототипы функций void use_local(); void use_static_local(); void use_global(); ///////////////////////////////////////////////////////////////////////////// int x = 1; // глобальная переменная ///////////////////////////////////////////////////////////////////////////// void main() < int x = 5; // переменная, локальная в main cout"local x="// скобки начинает новую область действия int x = 7; // скрывает х во внешней области действия cout"local x inside < =" // конец новой области действия cout"local x after < ="<// use_static_local имеет статическую локальную х use_global(); // use_global использует глобальную х use_local (); // use_local повторно инициализирует свою локальную х use_static_local(); // статическая локальная х сохраняет значение use_global(); // глобальная х также сохраняет свое значение cout<///////////////////////////////////////////////////////////////////////////// // функция реинициализирует локальную х при каждом вызове void use_local() < int x = 25; // инициализируется при каждом вызове функции cout<///////////////////////////////////////////////////////////////////////////// // функция инициализирует статическую локальную переменную х // только при первом вызове функции; между вызовами этой функции // значение х сохраняется void use_static_local() < static int x = 50; // инициализируется при первом вызове функции cout"use_static_local: x="<///////////////////////////////////////////////////////////////////////////// // функция модифицирует глобальную переменную х при каждом вызове void use_global() < cout"use_global: x="<///////////////////////////////////////////////////////////////////////////// 
Константные переменные

Ключевое слово сonst означает что переменная инициализируется один раз после объявления и ее значение не модифицируемое:

const double pi=3.14159265358979;

Еще один вариант использования глобальных констант:

#define PI 3.14159265358979 

Глобальные переменные в Java: когда их использовать?

Java-университет

Глобальные переменные в Java: когда их использовать? - 1

Привет! В этой статье мы поговорим о глобальных переменных, об их объявлении и примерах уместного использования. Небольшое примечание: мы не будем рассматривать глобальные переменные класса, то есть те, доступ к которым есть в рамках какого-либо одного класса. Будем говорить о глобальных переменных всего приложения — тех, доступ к которым есть в рамках целого приложения.

Как создавать глобальные переменные

Глобальные переменные — это переменные, которые доступны отовсюду в приложении. Иначе говоря их область видимости — все приложение. Чтобы создать такую переменную в Java, необходимо в публичном классе создать публичную статическую переменную:

 public class Example

Переменные a , b и str — стали глобальными. Мы можем получить к ним прямой доступ из других классов внутри приложения:

 public class GlobalVarsDemo < public static void main(String[] args) < Example.a = 4; Example.b = 5; Example.str = "Global String variable value"; System.out.println(Example.a); System.out.println(Example.b); System.out.println(Example.str); >> 

Если мы запустим метод main , то увидим следующий вывод:

 4 5 Global String variable value 
  • переменные которые можно редактировать;
  • переменные, которые можно только считывать.
 public class Constants

Глобальные переменные в Java: когда их использовать? - 2

По соглашению об именовании в ЯП Java, все константы нужно именовать в верхнем регистре, разделяя слова символом нижнего подчеркивания. Итак, мы создали константы, и теперь мы не сможем изменять их значения:Однако, мы можем считывать их значения:

 public class HelloWorld < public static void main(String[] args) < System.out.println(Constants.HELLO_WORLD_STR); >> 
 Hello, World! 
 public class ConstantsDemo < public static void main(String[] args) < double r = 10; String message = String.format("Площадь круга с радиусом %f=%f", r, getCircleSquare(r)); System.out.println(message); >static double getCircleSquare(double r) < return Constants.PI * r * r; >> 
 Площадь круга с радиусом 10,000000=314,159265 

Стоит ли использовать глобальные переменные

Глобальные переменные в Java: когда их использовать? - 3

В интернете много статей, основной посыл которых такой: глобальные переменные — это зло, плохо и ужасно. Так ли это на самом деле? Попробуем привести плюсы и минусы глобальных переменных, чтобы каждый мог сделать вывод самостоятельно.Начнем с минусов. Представим себе приложение, в котором есть класс с глобальными переменными, доступными для чтения и редактирования. Со временем в проекте растет количество классов, количество глобальных переменных и методов, которые используют глобальные переменные, а иными словами — зависят от них. Со временем каждая глобальная переменная считывается в разных частях системы для разных целей. В разных частях системы значение переменной может обновляться. Общая картина мира данного приложения существенно усложняется, и отсюда вытекают такие минусы :

  1. Снижение читабельности и увеличение сложности понимания кода.
  2. Увеличение сложности сопровождения кода.
  3. Для изменения одной глобальной переменной, необходимо проанализировать весь код, чтобы не задать переменной невалидное для других частей системы значение.
  4. Увеличение ошибок, которые очень сложно отлаживать. Представим себе глобальную переменную, массив объектов. В одной части системы в данном массиве ожидаются например строки, а в другой части системы кто-то решил использовать числа с плавающей точкой. Вряд ли кому-то захочется в таком разбираться.
  5. Имена переменных могут совпасть, если вы используете в своем коде глобальные переменные, а также некоторые библиотеки, в которых в свою очередь также используются глобальные переменные. Это может привести к ошибкам как на стороне вашего приложения, так и на стороне используемой вами библиотеки.
  6. Увеличивается связность между различными частями системы, которые используют глобальные переменные. Стремиться нужно наоборот к слабой связанности кода. Лучше иметь много маленьких подсистем, слабо связанных друг с другом, чем одну здоровенную фиговину. Потому что мозгу легче разбираться с несколькими простыми вещами, чем с одной слишком сложной и запутанной штукой.
  7. Написание юнит-тестов усложняется, поскольку тесту не известно, какие глобальные переменные нужны и каким образом их необходимо проинициализировать.
  8. В многопоточных приложениях использование глобальных переменных разными потоками приводит к росту ошибок, которые сложно отлаживать, и к росту сложности проекта. Из-за этого необходимо настраивать доступ к таким переменным более правильно, обвешивая их синхронизациями и блокировками. В будущем это может привести к замкнутым блокировкам. Например, поток А заблокировал для своей работы переменную X, а поток B заблокировал для своей работы переменную Y, а потоку А теперь нужна переменная Y, а потоку B переменная X. В итоге, программа зависнет.

Но это все неточно. Это описание рисков, вероятность которых увеличивается с ростом проекта и ростом числа глобальных переменных в нем. Перейдем к плюсам :

  1. В маленьких проектах глобальные переменные — наиболее простая вещь для достижения работоспособности проекта.
  2. Иногда страх использования глобальных переменных приводит к еще большему усложнению проекта. Тогда программисты начинают создавать синглтоны и прибегать к прочим шаблонам проектирования.
  3. В программировании часто бывает нужно опираться на некоторые неизменные значения. Самое разумное — записать такие значения в виде константы, потому что только константы дают гарантию, что значение переменной не изменится со временем. Такие константы можно встретить сплошь и рядом ( Integer.MAX_VALUE , Integer.MIN_VALUE , Boolean.TRUE , Collections.EMPTY_LIST и пр.). Но программирование не ограничивается использованием стандартных библиотек. Часто бывает нужно писать какую то уникальную логику, в которой необходимо будет опираться на свои, уникальные константы. Поэтому порой использование констант (глобальных переменных, доступных только для чтения) действительно упрощает жизнь.

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

  1. Все, что пишет начинающий разработчик — по сути небольшой проект. И использование в его проектах глобальных переменных приучит его к использованию глобальных переменных везде.
  2. Лучше научиться сначала обходиться без «запретных приемчиков». А с опытом понимание, когда такие приемчики уместно применять, придет само.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *