Для создания переменных которые доступны во всех файлах программы используется класс памяти
Аннотация: Классы памяти и область действия. Автоматические переменные. Внешние переменные. Статические переменные. Внешние статические переменные. Регистровые переменные.
Классы памяти и область действия
Классы памяти языка Си дают возможность определить, с какими функциями связаны какие переменные, и как долго переменная сохраняется в программе. Мы уже упоминали, что локальные переменные известны только функциям, содержащим их. В языке Си предполагается также, что о глобальных переменных знают сразу несколько функций:
Мы сделали переменную ext внешней, описав ее вне любого определения функции. Внутри функции, использующей эту переменную, мы объявляем ее внешней при помощи ключевого слова extern , предшествующего спецификации типа переменной. Компилятор ищет определение этой переменной вне функции. Если бы мы опустили ключевое слово extern в функции critic( ) , то компилятор создал бы в функции critic новую переменную и тоже назвал бы ее ext . Тогда другая переменная ext , которая находится в main( ) , никогда не получила бы нового значения.
Каждая переменная имеет тип и принадлежит к некоторому классу памяти . Время жизни и область действия идентификатора определяются ассоциированным с ним классом памяти . Существуют четыре разновидности классов памяти :
auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор , и освобождается при выходе из блока. Слово auto является сокращением слова automatic .
static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto , для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.
extern - внешний - идентификаторы, называемые внешними, external , используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память , ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.
register - регистровый - идентификаторы, подобные идентификаторам типа auto . Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.
Если класс памяти идентификатора не указан явно, то его класс памяти задается положением его определения в тексте программы. Если идентификатор определяется внутри функции, тогда его класс памяти auto , в остальных случаях идентификатор имеет класс памяти extern .
Предположим, что имеется программа на языке Си , исходный текст которой содержится в нескольких файлах. Для разделения данных (для связи) в функциях в этих файлах используются идентификаторы, определенные как extern . Если функция ссылается на внешний идентификатор , то файл , содержащий его, должен иметь описание или определение этого идентификатора. Явное задание класса памяти extern указывает на то, что этот идентификатор определен в другом файле, и здесь ему память не выделяется, а его описание дано лишь для проверки типа и для генерации кода.
Для внешнего идентификатора память выделяется только в том случае, если класс памяти не указан явно.
Хотя описание внешнего идентификатора может встретиться во многих файлах, только один файл должен содержать определение внешнего идентификатора. Область действия внешних идентификаторов не ограничивается файлом, содержащим их определения, а включает также файлы с соответствующими описаниями, с классом памяти extern .
Определение класса памяти переменной зависит от того, где переменная описана и какое ключевое слово , если оно есть, используется.
Класс памяти позволяет установить два факта. Во-первых, определить, какие функции имеют доступ к переменной. Пределы, до которых переменная доступна, характеризуют ее область действия . Во-вторых, определить, как долго переменная находится в памяти. Теперь подробнее рассмотрим свойства каждого типа.
Автоматические переменные
По умолчанию переменные, описанные внутри функции, являются автоматическими. Можно, однако, это подчеркнуть явно с помощью ключевого слова auto:
Так поступают, если хотят, например, показать, что определение переменной не нужно искать вне функции.
Автоматические переменные имеют локальную область действия . Только функция , в которой переменная определена, знает ее. Другие функции могут использовать переменные с тем же самым именем, но это будут независимые переменные, находящиеся в разных ячейках памяти.
Автоматическая переменная начинает существовать при вызове функции, содержащей ее. Когда функция завершает свою работу и возвращает управление туда, откуда ее вызвали, автоматическая переменная исчезает. Область действия автоматической переменной ограничена блоком, т.е. < >, в котором переменная описана.
Классы памяти определяют область видимости и время жизни переменных и/или функций в программе, написанной на языке С. Они предшествуют типу, который модифицируют. В языке С различают четыре класса памяти:
- auto (автоматический);
- register (регистровый);
- static (статический);
- extern (внешний).
Класс памяти auto
Все локальные переменные по умолчанию принадлежат классу памяти auto , то есть являются автоматическими.
В приведенном примере две переменные определены в одном классе памяти. Использование auto возможно только внутри функций, т.е. для создания локальных переменных.
Класс памяти register
Класс памяти register используется для определения локальных переменных, которые должны храниться в регистре процессора, а не в оперативной памяти. Это означает, что переменная имеет максимальный размер, равный размеру регистра (обычно одно слово), и к ней не может быть применен унарный оператор & (из-за отсутствия места в памяти).
Класс register следует использовать только для переменных, требующих быстрого доступа, таких как счетчики. Следует также отметить, что определение register не означает, что переменная обязательно будет храниться в register . Это означает, что она МОЖЕТ храниться в register в зависимости от аппаратных ограничений и ограничений, связанных с выполнением программы.
Класс памяти static
Класс памяти static инструктирует компилятор поддерживать локальную переменную в течение всего времени жизни программы вместо того, чтобы создавать и уничтожать ее каждый раз, когда она входит и выходит из области видимости. Таким образом, статичность локальных переменных позволяет им сохранять свои значения между вызовами функций.
Модификатор static также может быть применен к глобальным переменным. В этом случае область видимости переменной ограничивается файлом, в котором она объявлена.
В программировании на языке С применение static к глобальной переменной приводит к тому, что только один экземпляр этого члена разделяется всеми объектами его класса:
Класс памяти extern
Класс памяти extern используется для того, чтобы дать ссылку на глобальную переменную, которая видна ВСЕМ файлам программы. Использование extern не может инициализировать переменную, однако указывает на имя переменной в месте памяти, определенное ранее.
При наличии нескольких файлов и определении глобальной переменной или функции, которая также будет использоваться в других файлах, extern применяется в другом файле для обеспечения ссылки на определенную переменную или функцию. Проще говоря, extern используется для объявления глобальной переменной или функции в другом файле.
Модификатор extern чаще всего применяется при наличии двух или более файлов, в которых используют одни и те же глобальные переменные или функции, как показано ниже.
Первый файл: main.c
Второй файл: support.c
Как видите, extern используется для объявления count во втором файле, тогда как определение count находится в первом файле main.c. Теперь скомпилируем эти два файла следующим образом:
Получится исполнимая программа a.out. Ее выполнение приведет к следующему результату:
Теги: Классы памяти, auto, register, extern, static, объявление переменной, определение переменной, константный инициализатор.
В си определено несколько классов памяти для переменных и функций. Они изменяют область видимости переменных и функций, определяют время жизни объекта и расположение в памяти.
Классы памяти переменных
П о умолчанию, локальные переменные имеют класс auto. Такие переменные располагаются на стеке а их область видимости ограничена своим блоком. Запись
Очевидно, что глобальные переменные не могут быть объявлены как auto, потому что располагаются в data-сегменте.
Следующий класс памяти – register. Когда мы определяем регистровую переменную, то мы просим компилятор, чтобы переменная располагалась в регистре, а не в оперативной памяти. Компилятор может сделать переменную регистровой, если позволяют условия (регистры не заняты, и по мнению компилятора это не приведёт к увеличению издержек). Регистровые переменные определяются с помощью служебного слово register перед типом
Так как регистровая переменная не имеет адреса, то к ней не применима операция взятия адреса, это вызовет ошибку во время компиляции. Аргументы функции также могут быть заданы как register. Внутри функции они будут вести себя также, как и регистровые переменные.
Следующий класс памяти – статический. Переменные, объявленные как static, хранятся в data или в bss сегменте. Отличительной чертой является то, что время их жизни совпадает с временем жизни приложения, как и у глобальных переменных. Но в отличие от глобальных переменных, область видимости ограничена только блоком, в котором они определены.
В этом примере переменные prevArg и prevAns инициализируются единожды, и не уничтожаются после выхода из функции. Переменная prevArg используется для хранения предыдущего аргумента функции, а prevAns для хранения предыдущего результата. Если аргумента функции совпадает с предыдущим, то возвращается ранее вычисленное значение, иначе оно вычисляется по-новому.
Другой показательный пример – функция-генератор, которая при каждом вызове возвращает новое значение.
Если бы служебное слово static отсутствовало, то каждый раз при вызове функции локальная переменная counter снова создавалась, инициализировалась и уничтожалась после выхода из функции.
Статическая переменная может иметь только константную инициализацию. Например, она не может быть инициализирована вызовом функции.
Переменная, объявленная как static, должна иметь только один экземпляр в данной области видимости и вне этой области видимости не видна. Глобальная переменная, объявленная как static, видна только в своём файле.
Напротив, переменная, объявленная как extern может быть использована в других файлах при условии, что она была определена.
Объявление и определение переменной.
В глобальном контексте переменная сначала требует объявления. Таким образом, компилятор будет знать её имя и тип. Определение переменной требует выделения под неё памяти и инициализации. Посмотрите следующий код. Он абсолютно легален и должен работать по стандарту
Теперь, что будет, если одновременно объявить переменную и инициализировать её. Это определение переменной, которое требует её объявления
Следующая программа не скомпилируется
Это связано с тем, что отсутствует определение переменной. Если определить переменную внутри main, то это будет уже другой экземпляр переменной, которая будет расположена на стеке. Вообще, при работе с одним файлом использование extern переменных не оправдано. Рассмотрим ситуацию, когда у нас имеются ещё два файла – заголовочный File1.h и File1.c. В заголовочном файле объявим extern переменную Global
в файле исходного кода определим её
После подключения файла File1.h можно использовать эту переменную в файле main.c, при этом гарантировано, что существует только один экземпляр этой переменной для всех файлов проекта
Если теперь определим функцию, которая изменяет эту переменную, то все функции из всех файлов будут видеть эти изменения.
File1.h:
Вывод
From main: Global = 567
from File1: Global = 567
changed to 1234
From main: Global = 1234
Класс памяти для функций
Ф ункции по умолчанию определены как extern, это значит, что они видны всем, кто подключит данный файл. То есть, запись
Другой класс - static, делает функцию видимой только внутри своего модуля.
Рассмотрим пример – у нас будет, как обычно 2 файла, File1.h и File1.c. В первом определим две функции, одну extern, а вторую static
Заметьте: мы не сможем вызвать функцию hidden вне файла File1.c, но внутри файла эта функция доступна.
Для функций также, как и для переменных, различают объявление и определение. Объявление обычно прячут в заголовочный файл, также как и объявление переменных. Определения находятся в си файле.
Всё ещё не понятно? – пиши вопросы на ящик
Теги: Си память, malloc, calloc, realloc, free, Ошибки выделения памяти, Висячие указатели, Динамические массивы, Многомерные динамические массивы.
malloc
В предыдущей главе уже обсуждалось, что локальные переменные кладутся на стек и существую до тех пор, пока мы не вышли из функции. С одной стороны, это позволяет автоматически очищать память, с другой стороны, существует необходимость в переменных, время жизни которых мы можем контролировать самостоятельно. Кроме того, нам необходимо динамическое выделение памяти, когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выдеение памяти – достаточно дорогостоящая операция.
Для выделения памяти на куче в си используется функция malloc (memory allocation) из библиотеки stdlib.h
Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.
После того, как мы поработали с памятью, необходимо освободить память функцией free.
Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.
Здесь (int *) – приведение типов. Пишем такой же тип, как и у указателя.
size * sizeof(int) – сколько байт выделить. sizeof(int) – размер одного элемента массива.
После этого работаем с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память.
Теперь представим на рисунке, что у нас происходило. Пусть мы ввели число 5.
Функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может пользоваться и любым другим адресом.
Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У нас будет гарантия, что компьютер не отдаст нашу память кому-то ещё. Когда мы вызываем функцию free, то мы освобождаем память, то есть говорим компьютеру, что эта память может быть использована кем-то другим. Он может использовать нашу память, а может и нет, но теперь у нас уже нет гарантии, что эта память наша. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.
Это очень похоже на съём номера в отеле. Мы получаем дубликат ключа от номера, живём в нём, а потом сдаём комнату обратно. Но дубликат ключа у нас остаётся. Всегда можно зайти в этот номер, но в нём уже кто-то может жить. Так что наша обязанность – удалить дубликат.
Иногда думают, что происходит "создание" или "удаление" памяти. На самом деле происходит только перераспределение ресурсов.
Освобождение памяти с помощью free
Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?
- 1. Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
- 2. Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.
Работа с двумерными и многомерными массивами
Д ля динамического создания двумерного массива сначала необходимо создать массив указателей, после чего каждому из элементов этого массива присвоить адрес нового массива.
Для удаления массива необходимо повторить операцию в обратном порядке - удалить сначала подмассивы, а потом и сам массив указателей.
- 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
- 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.
Создадим "треугольный" массив и заполним его значениями
Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.
calloc
Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис
realloc
Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:
Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.
Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.
Ошибки при выделении памяти
1. Бывает ситуация, при которой память не может быть выделена. В этом случае функция malloc (и calloc) возвращает NULL. Поэтому, перед выделением памяти необходимо обнулить указатель, а после выделения проверить, не равен ли он NULL. Так же ведёт себя и realloc. Когда мы используем функцию free проверять на NULL нет необходимости, так как согласно документации free(NULL) не производит никаких действий. Применительно к последнему примеру:
Хотелось бы добавить, что ошибки выделения памяти могут случиться, и просто выходить из приложения и выкидывать ошибку плохо. Решение зависит от ситуации. Например, если не хватает памяти, то можно подождать некоторое время и после этого опять попытаться выделить память, или использовать для временного хранения файл и переместить туда часть объектов. Или выполнить очистку, сократив используемую память и удалив ненужные объекты.
2. Изменение указателя, который хранит адрес выделенной области памяти. Как уже упоминалось выше, в выделенной области хранятся данные об объекте - его размер. При удалении free получает эту информацию. Однако, если мы изменили указатель, то удаление приведёт к ошибке, например
Таким образом, если указатель хранит адрес, то его не нужно изменять. Для работы лучше создать дополнительную переменную указатель, с которой работать дальше.
3. Использование освобождённой области. Почему это работает в си, описано выше. Эта ошибка выливается в другую – так называемые висячие указатели (dangling pointers или wild pointers). Вы удаляете объект, но при этом забываете изменить значение указателя на NULL. В итоге, он хранит адрес области памяти, которой уже нельзя воспользоваться, при этом проверить, валидная эта область или нет, у нас нет возможности.
Эта программа отработает и выведет мусор, или не мусор, или не выведет. Поведение не определено.
Если же мы напишем
то программа выкинет исключение. Это определённо лучше, чем неопределённое поведение. Если вы освобождаете память и используете указатель в дальнейшем, то обязательно обнулите его.
4. Освобождение освобождённой памяти. Пример
Здесь дважды вызывается free для переменной a. При этом, переменная a продолжает хранить адрес, который может далее быть передан кому-нибудь для использования. Решение здесь такое же как и раньше - обнулить указатель явно после удаления:
5. Одновременная работа с двумя указателями на одну область памяти. Пусть, например, у нас два указателя p1 и p2. Если под первый указатель была выделена память, то второй указатель может запросто скомпрометировать эту область:
Рассмотрим код ещё раз.
Теперь оба указателя хранят один адрес.
А вот здесь происходит непредвиденное. Мы решили выделить под p2 новый участок памяти. realloc гарантирует сохранение контента, но вот сам указатель p1 может перестать быть валидным. Есть разные ситуации. Во-первых, вызов malloc мог выделить много памяти, часть которой не используется. После вызова ничего не поменяется и p1 продолжит оставаться валидным. Если же потребовалось перемещение объекта, то p1 может указывать на невалидный адрес (именно это с большой вероятностью и произойдёт в нашем случае). Тогда p1 выведет мусор (или же произойдёт ошибка, если p1 полезет в недоступную память), в то время как p2 выведет старое содержимое p1. В этом случае поведение не определено.
Два указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.
Различные аргументы realloc и malloc.
При вызове функции malloc, realloc и calloc с нулевым размером поведение не определено. Это значит, что может быть возвращён как NULL, так и реальный адрес. Им можно пользоваться, но к нему нельзя применять операцию разадресации.
Вызов realloc(NULL, size_t) эквиваленте вызову malloc(size_t).
Однако, вызов realloc(NULL, 0) не эквивалентен вызову malloc(0) :) Понимайте это, как хотите.
Примеры
1. Простое скользящее среднее равно среднему арифметическому функции за период n. Пусть у нас имеется ряд измерений значения функции. Часто эти измерения из-за погрешности "плавают" или на них присутствуют высокочастотные колебания. Мы хотим сгладить ряд, для того, чтобы избавиться от этих помех, или для того, чтобы выявить общий тренд. Самый простой способ: взять n элементов ряда и получить их среднее арифметическое. n в данном случае - это период простого скользящего среднего. Так как мы берём n элементов для нахождения среднего, то в результирующем массиве будет на n чисел меньше.
Это простой пример. Большая его часть связана со считыванием данных, вычисление среднего всего в девяти строчках.
2. Сортировка двумерного массива. Самый простой способ сортировки - перевести двумерный массив MxN в одномерный размером M*N, после чего отсортировать одномерный массив, а затем заполнить двумерный массив отсортированными данными. Чтобы не тратить место под новый массив, мы поступим по-другому: если проходить по всем элементам массива k от 0 до M*N, то индексы текущего элемента можно найти следующим образом:
j = k / N;
i = k - j*M;
Заполним массив случайными числами и отсортируем
3. Бином Ньютона. Создадим треугольную матрицу и заполним биномиальными коэффициентами
Если Вы желаете изучать этот материал с преподавателем, советую обратиться к репетитору по информатике
Всё ещё не понятно? – пиши вопросы на ящик
Аннотация: Классы памяти и область действия. Автоматические переменные. Внешние переменные. Статические переменные. Внешние статические переменные. Регистровые переменные.
Классы памяти и область действия
Классы памяти языка Си дают возможность определить, с какими функциями связаны какие переменные, и как долго переменная сохраняется в программе. Мы уже упоминали, что локальные переменные известны только функциям, содержащим их. В языке Си предполагается также, что о глобальных переменных знают сразу несколько функций:
Мы сделали переменную ext внешней, описав ее вне любого определения функции. Внутри функции, использующей эту переменную, мы объявляем ее внешней при помощи ключевого слова extern , предшествующего спецификации типа переменной. Компилятор ищет определение этой переменной вне функции. Если бы мы опустили ключевое слово extern в функции critic( ) , то компилятор создал бы в функции critic новую переменную и тоже назвал бы ее ext . Тогда другая переменная ext , которая находится в main( ) , никогда не получила бы нового значения.
Каждая переменная имеет тип и принадлежит к некоторому классу памяти . Время жизни и область действия идентификатора определяются ассоциированным с ним классом памяти . Существуют четыре разновидности классов памяти :
auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор , и освобождается при выходе из блока. Слово auto является сокращением слова automatic .
static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto , для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.
extern - внешний - идентификаторы, называемые внешними, external , используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память , ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.
register - регистровый - идентификаторы, подобные идентификаторам типа auto . Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.
Если класс памяти идентификатора не указан явно, то его класс памяти задается положением его определения в тексте программы. Если идентификатор определяется внутри функции, тогда его класс памяти auto , в остальных случаях идентификатор имеет класс памяти extern .
Предположим, что имеется программа на языке Си , исходный текст которой содержится в нескольких файлах. Для разделения данных (для связи) в функциях в этих файлах используются идентификаторы, определенные как extern . Если функция ссылается на внешний идентификатор , то файл , содержащий его, должен иметь описание или определение этого идентификатора. Явное задание класса памяти extern указывает на то, что этот идентификатор определен в другом файле, и здесь ему память не выделяется, а его описание дано лишь для проверки типа и для генерации кода.
Для внешнего идентификатора память выделяется только в том случае, если класс памяти не указан явно.
Хотя описание внешнего идентификатора может встретиться во многих файлах, только один файл должен содержать определение внешнего идентификатора. Область действия внешних идентификаторов не ограничивается файлом, содержащим их определения, а включает также файлы с соответствующими описаниями, с классом памяти extern .
Определение класса памяти переменной зависит от того, где переменная описана и какое ключевое слово , если оно есть, используется.
Класс памяти позволяет установить два факта. Во-первых, определить, какие функции имеют доступ к переменной. Пределы, до которых переменная доступна, характеризуют ее область действия . Во-вторых, определить, как долго переменная находится в памяти. Теперь подробнее рассмотрим свойства каждого типа.
Автоматические переменные
По умолчанию переменные, описанные внутри функции, являются автоматическими. Можно, однако, это подчеркнуть явно с помощью ключевого слова auto:
Так поступают, если хотят, например, показать, что определение переменной не нужно искать вне функции.
Автоматические переменные имеют локальную область действия . Только функция , в которой переменная определена, знает ее. Другие функции могут использовать переменные с тем же самым именем, но это будут независимые переменные, находящиеся в разных ячейках памяти.
Автоматическая переменная начинает существовать при вызове функции, содержащей ее. Когда функция завершает свою работу и возвращает управление туда, откуда ее вызвали, автоматическая переменная исчезает. Область действия автоматической переменной ограничена блоком, т.е. < >, в котором переменная описана.
Читайте также: