Перейти к содержимому

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

  • автор:

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

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

Отслеживать
219k 15 15 золотых знаков 119 119 серебряных знаков 230 230 бронзовых знаков
задан 3 апр 2021 в 9:36
ТарасПрогер ТарасПрогер
2,279 1 1 золотой знак 7 7 серебряных знаков 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,491 4 4 серебряных знака 21 21 бронзовый знак

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

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

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

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

Все компиляторы языка программирования генерируют код, который во время выполнения хранится в оперативной памяти. Размещение данных в памяти, чтобы память была зарезервирована в 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 

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

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

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

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

def rectangle(): a = float(input("Ширина: ")) b = float(input("Высота: ")) print("Площадь: %.2f" % (a * b)) def triangle(): a = float(input("Основание: ")) h = float(input("Высота: ")) print("Площадь: %.2f" % (0.5 * a * h)) figure = input("1-прямоугольник, 2-треугольник: ") if figure == '1': rectangle() elif figure == '2': triangle()

Сколько здесь переменных? Какие из них являются глобальными, а какие – локальными?

Здесь пять переменных. Глобальной является только figure . Переменные a и b из функции rectangle , а также a и h из triangle – локальные. При этом локальные переменные с одним и тем же идентификатором a , но объявленные в разных функциях, – разные переменные.

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

В приведенной программе к глобальной области видимости относятся заголовки объявлений функций, объявление и присваивание переменной figure , конструкция условного оператора.

К локальной области относятся тела функций. Если, находясь в глобальной области видимости, мы попытаемся обратиться к локальной переменной, то возникнет ошибка:

elif figure == '2': triangle() print(a)
1-прямоугольник, 2-треугольник: 2 Основание: 4 Высота: 5 Площадь: 10.00 Traceback (most recent call last): File "test.py", line 17, in print(a) NameError: name 'a' is not defined

Однако мы можем обращаться из функций к глобальным переменным:

def rectangle(): a = float(input("Ширина %s: " % figure)) b = float(input("Высота %s: " % figure)) print("Площадь: %.2f" % (a * b)) def triangle(): a = float(input("Основание %s: " % figure)) h = float(input("Высота %s: " % figure)) print("Площадь: %.2f" % (0.5 * a * h)) figure = input("1-прямоугольник, 2-треугольник: ") if figure == '1': rectangle() elif figure == '2': triangle()
1-прямоугольник, 2-треугольник: 1 Ширина 1: 6.35 Высота 1: 2.75 Площадь: 17.46

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

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

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

def rectangle(): a = float(input("Ширина: ")) b = float(input("Высота: ")) result = a * b def triangle(): a = float(input("Основание: ")) h = float(input("Высота: ")) result = 0.5 * a * h result = 0 figure = input("1-прямоугольник, 2-треугольник: ") if figure == '1': rectangle() elif figure == '2': triangle() print("Площадь: %.2f" % result)

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

1-прямоугольник, 2-треугольник: 2 Основание: 6 Высота: 4.5 Площадь: 0.00

… что-то пошло не так.

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

Когда функция завершает свою работу, то значение локальной result теряется, а глобальная не была изменена.

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

В случае с result он ничего не ищет. Он выполняет вычисления справа от знака присваивания, создает локальную переменную result , связывает ее с полученным значением.

На самом деле можно принудительно обратиться к глобальной переменной. Для этого существует команда global :

def rectangle(): a = float(input("Ширина: ")) b = float(input("Высота: ")) global result result = a * b def triangle(): a = float(input("Основание: ")) h = float(input("Высота: ")) global result result = 0.5 * a * h result = 0 figure = input("1-прямоугольник, 2-треугольник: ") if figure == '1': rectangle() elif figure == '2': triangle() print("Площадь: %.2f" % result)

В таком варианте программа будет работать правильно.

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

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

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

Практическая работа

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

В основной ветке программы вызывается функция cylinder() , которая вычисляет площадь цилиндра. В теле cylinder определена функция circle , вычисляющая площадь круга по формуле πr 2 . В теле cylinder у пользователя спрашивается, хочет ли он получить только площадь боковой поверхности цилиндра, которая вычисляется по формуле 2πrh, или полную площадь цилиндра. В последнем случае к площади боковой поверхности цилиндра должен добавляться удвоенный результат вычислений функции circle() .

Как вы думаете, можно ли из основной ветки программы вызвать функцию, вложенную в другую функцию? Почему?

Примеры решения и дополнительные уроки в pdf-версии курса

X Скрыть Наверх

Python. Введение в программирование

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

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

В следующей программе можно увидеть, что переменная 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);
>

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

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

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