C Урок 23. Время жизни в C. Часть 2



 

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

Откроем файл main.c и в функции main() добавим перед нашими локальными переменными спецификатор auto

 

 

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

Закомментируем раскомментированный участок кода и раскомментируем самый первый участок кода

 

 

Добавим к данному коду ещё вот такой

 

 

Мы сравниваем между собой две переменные и если условие выполняется, то мы выводим число, которое мы присваиваем переменной во время объявления. Причём мы это объявление делаем прямо в теле оператора if.

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

 

 

Всё отлично сработало. Но нам просто повезло, что у нас условие выполнилось, а если бы не выполнилось, то мы бы ничего не увидели. Что же делать нам, если в случае невыполнения условия мы хотим увидеть другую цифру в качестве значения переменной n? Казалось бы, что нам надо добавить вот такой блок к оператору if

 

 

Но мы уже видим, что у нас подчёркнута красным переменная n.

Попробуем собрать код

 

 

Мы видим сообщение об ошибке, гласящее о том, что переменная n не объявлена.

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

Мы либо должны объявить переменную n где-то вне блока, то есть уровнем выше наших блоков, например вначале функции или перед конструкцией if else, либо объявить ещё раз в блоке else. Воспользуемся последним способом

 

 

Теперь у нас всё соберётся и сработает. Можно попробовать изменить условие, чтобы увидеть работу блока else

 

if(xf<yf)

 

Соберём код и посмотрим результат

 

 

Всё отлично. Использование переменных в отдельных блоках даёт нам право использовать те же имена в других.

Давайте ещё немного поработаем с блоками.

Добавим вот такой код

 

 

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

 

 

Казалось бы, ерунда какая-то а не код. Мы объявляем цикл на 3 итерации, а в нём ещё вложенный цикл на 5 итераций. Мы уже заранее знаем, что мы в результате всех своих операций в 15 местах получим в качестве результата нули, так как независимо от инкрементирования переменной n в конце блока в следующей итерации она зануляется.

 

 

Соберём наш код и увидим, что так оно и есть

 

 

Но если мы в объявлении переменной n применим спецификатор static, то наш код уже обретёт смысл

 

static int n=0;

 

Соберём код и посмотрим на результат

 

 

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

 

 

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

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

 

 

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

Создадим на нашу функцию прототип в начале файла

 

 

 

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

 

 

Проверим работу нашего кода

 

 

Так и есть. У нас нули.

Вернёмся в функцию my_counter и добавим к объявлению нашей переменной спецификатор static

 

static int i=0;

 

Проверим работу нашего кода

 

 

Теперь у нас работает наращивание счётчика. Таким образом мы придали ещё одной переменной 'вечную жизнь' при помощи спецификатора static.

Вернёмся в нашу функцию main, закомментируем наш участок кода и добавим вот такой

 

 

Мы будем выводить числа в консосль от 1 до 10. Соберём код и посмотрим результат

 

 

Всё работает. Теперь посмотрим в отладке, где собственно выделилась память под переменную i.

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

 

 

Сохраним данный файл и теперь запустим наш проект в отладке, открыв затем окно дизассемблера. Можно даже не запускать проект и не ставить никаких брейкпоинтов

 

 

Во первых, самое главное то, что код у нас в дизассемблере имеет стиль intel. Чудеса да и только! Также мы видим, что переменная наша попала в стек с определённым смещением, и потом она при вызове подпрограммы, содержащей функцию printf, из этого адреса и извлекается. Отлично.

Вернёмся в код и объявим теперь нашу переменную со спецификатором register

 

register int i = 0;

 

Если мы соберём наш код, то при этом результат работы никак не изменится, по крайней мере в его визуальном отношении.

Теперь снова запустим отладку и посмотрим в дизассемблере, куда транслятор положил нашу переменную

 

 

Теперь мы видим, что память под переменную выделилась в регистре процессора ebx и при вызове функции printf оттуда и извлекается значение данной переменной.

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

Закомментируем данный участок кода и раскомментируем следующий

 

 

Соберём наш код и посмотрим результат

 

 

Результат кода во второй строке (где считается сумма) неправильный. Почему же так происходит? Да потому что в видеоверсии урока 21 я допустил ошибку. Поэтому перейдём в файл ariph.c и посмотрим вот этот участок функции my_sum

 

 

Там деление, а не сумма. Исправим это

 

return yf+zf;

 

Теперь будет всё правильно работать

 

 

Вернёмся в файл main.c и добавим спецификатор static нашим глобальным переменным, которые мы используем в операциях

 

static float yf, zf;

 

Попробуем собрать код и мы получим целую кучу ошибок

 

 

Всё потому, что наши переменные теперь видны только в модуле main. Поэтому уберём данный спецификатор.

Вернёмся опять в файл ariph.h и добавим такой же спецификатор к заголовку функции my_sum

 

static float my_sum(void)

 

Такой же спецификатор мы добавим и в прототипе функции в заголовочном файле

 

static float my_sum(void);

 

Если мы попытаемся собрать файл, то мы получим сообщение об ошибке, что функция данная у нас не объявлена

 

 

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

Уберём спецификатор static из обеих мест и пересоберём наш проект. Теперь всё работает.

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

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование на C Следующий урок

 

 

Исходный код

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

C Время жизни в C

Один комментарий на “C Урок 23. Время жизни в C. Часть 2
  1. Sonya Freedmann:

    а каким образом в дизассемблере включить вывод самих команд С? У меня выводятся толко команды ассемблера.

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

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

*