Урок №49. Глобальные переменные

  Юрий  | 

  |

  Обновл. 24 Янв 2022  | 

 143824

 ǀ   18 

Мы уже знаем, что переменные, объявленные внутри блока, называются локальными. Они имеют локальную область видимости (используются только внутри блока, в котором объявлены) и автоматическую продолжительность жизни (создаются в точке определения и уничтожаются в конце блока).

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

Определение глобальных переменных

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

Подобно тому, как переменные во внутреннем блоке скрывают переменные с теми же именами во внешнем блоке, локальные переменные скрывают глобальные переменные с одинаковыми именами внутри блока, в котором они определены. Однако с помощью оператора разрешения области видимости (::), компилятору можно сообщить, какую версию переменной вы хотите использовать: глобальную или локальную. Например:



Результат выполнения программы:

Global value: 3
Local value: 9

Использовать одинаковые имена для локальных и глобальных переменных — это прямой путь к проблемам и ошибкам, поэтому подобное делать не рекомендуется. Многие разработчики добавляют к глобальным переменным префикс g_ («g» от англ. «global»). Таким образом, можно убить сразу двух зайцев: определить глобальные переменные и избежать конфликтов имен с локальными переменными.

Ключевые слова static и extern


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

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

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

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

Если вы хотите сделать глобальную переменную внутренней (которую можно использовать только внутри одного файла) — используйте ключевое слово static:

Аналогично, если вы хотите сделать глобальную переменную внешней (которую можно использовать в любом файле программы) — используйте ключевое слово extern:

По умолчанию, неконстантные переменные, объявленные вне блока, считаются внешними. Однако константные переменные, объявленные вне блока, считаются внутренними.

Предварительные объявления переменных с использованием extern

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

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

global.cpp:

main.cpp:

Если предварительное объявление находится вне блока, то оно применяется ко всему файлу. Если же внутри блока, то оно применяется только к нему.

Если переменная объявлена с помощью ключевого слова static, то получить доступ к ней с помощью предварительного объявления не получится. Например:

constants.cpp:

main.cpp:

Обратите внимание, если вы хотите объявить неинициализированную неконстантную глобальную переменную, то не используйте ключевое слово extern, иначе C++ будет думать, что вы пытаетесь записать предварительное объявление.

Связи функций


Функции имеют такие же свойства связи, что и переменные. По умолчанию они имеют внешнюю связь, которую можно сменить на внутреннюю с помощью ключевого слова static:

Предварительные объявления функций не нуждаются в ключевом слове extern. Компилятор может определить сам (по телу функции): определяете ли вы функцию или пишете её прототип.

Файловая область видимости vs. Глобальная область видимости

Термины «файловая область видимости» и «глобальная область видимости», как правило, вызывают недоумение, и это отчасти объясняется их неофициальным использованием. В теории, в языке C++ все глобальные переменные имеют файловую область видимости. Однако, по факту, термин «файловая область видимости» чаще применяется к внутренним глобальным переменным, а «глобальная область видимости» — к внешним глобальным переменным.

Например, рассмотрим следующую программу:

global.cpp:

main.cpp:

Переменная g_y имеет файловую область видимости внутри global.cpp. Доступ к этой переменной вне файла global.cpp отсутствует. Обратите внимание, хотя эта переменная и используется в main.cpp, сам main.cpp не видит её, он видит только предварительное объявление g_y (которое также имеет файловую область видимости). Линкер отвечает за связывание определения g_y в global.cpp с использованием g_y в main.cpp.

Глобальные символьные константы


На уроке о символьных константах, мы определяли их следующим образом:

constants.h:

Хоть это просто и отлично подходит для небольших программ, но каждый раз, когда constants.h подключается в другой файл, каждая из этих переменных копируется в этот файл. Таким образом, если constants.h подключить в 20 различных файлов, то каждая из переменных продублируется 20 раз. Header guards не остановят это, так как они только предотвращают подключение заголовочного файла более одного раза в один файл. Дублирование переменных на самом деле не является проблемой (поскольку константы зачастую не занимают много памяти), но изменение значения одной константы потребует перекомпиляции каждого файла, в котором она используется, что может привести к большим временным затратам в более крупных проектах.

Избежать этой проблемы можно, превратив эти константы в константные глобальные переменные, и изменив заголовочный файл только для хранения предварительных объявлений переменных. Например:

constants.cpp:

constants.h:

Их использование в коде остается неизменным:

Теперь определение символьных констант выполняется только один раз (в constants.cpp). Любые изменения, сделанные в constants.cpp, потребуют перекомпиляции только (одного) этого файла.

Но есть и обратная сторона медали: такие константы больше не будут считаться константами типа compile-time и, поэтому, не смогут использоваться где-либо, где потребуется константа такого типа.

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

Предостережение о (неконстантных) глобальных переменных

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

Заключение

Подытожим вышесказанное:

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

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

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

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

Тест

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

Ответ

   Область видимости определяет, где переменная доступна для использования. Продолжительность жизни определяет, где переменная создается и где уничтожается. Связь определяет, может ли переменная использоваться в другом файле или нет.

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

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

   Глобальные переменные могут иметь либо внутреннюю, либо внешнюю связь (это можно изменить через использование ключевых слов static и extern, соответственно).

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (497 оценок, среднее: 4,87 из 5)
Загрузка...

Комментариев: 18

  1. Powarito:

    Насчет пункта «Глобальные символьные константы» — проблема в том, что тот метод, который тут предоставлен, и вправду решает проблему, когда возможна перекомпиляция, к примеру, 20 файлов, как было описано, НО эти консанты на самом деле не константы времени компиляции (compile-time const), а времени запуска програмы (run-time const). И вправду — сначала они объявляются, а потом уже им присваиваются значения в .cpp-файле на этапе линкинга, но не компиляции, как требуется.
    Тоесть такие константы не подойдут, например, для статических массивов (тоесть выделеных на стеке, а не динамически на куче).

  2. Николай:

    Единственный способ проверить утверждение статичности константы — это взятие адреса данной переменной (пока не проходили).

    Я написал простейшую программу, состоящую из 3-х файлов:

    test_const.h

    print_const.cpp

    test_const.cpp

    И получил ожидаемый результат:

    Static const address:
    1 [0x402004]
    1 [0x402040]

    Extern const address:
    2 [0x402044]
    2 [0x402044]

    В первом случае (STATIC_CONST = 1) адреса получились разными. Это говорит о том, что при константы создавались во время построения объектного файла для каждого из файлов *.cpp.

    Во втором случае (EXTERN_CONST = 2) адреса получились одинаковыми. Это говорит о том, что данная константа была создана только во время построения объектного файла для print_const.cpp, а затем использовалась ссылка на эту константу во время построения файла для test_const.cpp.

  3. Александр:

    Здравствуйте
    Я не совсем понял момент с копированием переменных 20 раз
    что будет если сделать вот так?
    не писать ключевое слово extern в constants.cpp
    код скомпилируется, но как понять, создается множество копий или нет?

    constants.cpp:

    constants.h:

    1. Владислав:

      Не скомпилируется, так как без объявления extern для константных переменных не будет связи с твоим Constants.cpp

      1. Александр:

        Возможно я что-то не так сделал, я уже не помню. Жаль, что не так быстро тут отвечают на комментарии)
        Но у меня компилировалось. Я часть кода скопировал сюда как было.

        1. Владимир:

          Не внимательно читали предыдущие уроки, в каком точно не помню но этот механизм объясняли. Постараюсь ответить кратко про "копирование 20 раз":

          Вот у вас есть файл constants.h в котором вы что то написали, везде в остальных файлах где вы напишите #include "constants.h", будет скопировано полностью содержимого этого файла.

          Короче #include "constants.h" работает следующим образом, на этапе сборки, компилятор видит команду #include и затем имя файла в "" или <> скобках, он(компилятор) удаляет эту строку с командой и в это место где она была, он копирует ВСЕ содержимое того файла, имя которого там было написано.

          Даже если у тебя программа состоит из одной строчки вывода сообщения

          В верху файла присутствует строчка #include <iostream>, в котором тысячи строк. И по факту при компиляции, компилятор скопирует эти тысячи строк из того файла в твой, в то место где была строчка #include <iostream>. Вот так примерно это работает…

  4. Evgeny.A:

    А как же volatile ?

    1. Zigmobil:

      Ну да, на 49 уроке только про многопоток и говорить

  5. Slava:

    По поводу примера "Глобальные символьные константы". Имеет ли смысл создавать extern const double переменные в cpp файле, если можно оставить просто const double? Ведь extern уже определен в заголовочном файле.

  6. Яна:

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

  7. Владимир:

    Возникает вопрос в связи вот с чем:
    "…Таким образом, если constants.h подключить в 20 различных файлов, то каждая из переменных продублируется 20 раз. Header guards не остановят это, так как они только предотвращают подключение заголовочного файла более одного раза в один файл. …"
    Мы изучали, что директивы условной компиляции предотвращают или позволяют именно компиляцию по заданному условию. Из них и формируются Header guards.
    Как тогда могут 20 раз дублироваться переменные, если компиляция возможна лишь раз? Дублируются эти 20 раз уже откомпилированные за первый раз переменные в виде машинного кода, или как-то еще?

    1. Константин:

      Подключая заголовок.h к 20 различным файлам.cpp, в тем самым в каждый файл.cpp вставляете содержимое заголовков, всего получается что по одному разу на каждый файл.cpp, поэтому получается, что у вас создаться по 20 копий константных переменных, которые были в заголовке.h. Они не подключаются 20 раз в один файл, а по одному разу в каждый из 20 файлов. Как и написано в уроке, избежать этого можно написав в заголовке не определение, а объявление констант, то есть константы будут определены один раз в одном файле.cpp, а в заголовке будут определения. Так что меняя константы, вам придётся перекомпилировать лишь один файл.cpp, а всё остальное останется без изменений. Но в первом случае, вам бы пришлось перекомпилировать все 20 файлов.

  8. Денис:

    Не могли бы Вы пояснить, в каких случаях применение extern является обязательным а в каких нет.

  9. Денис:

    Добрый день! В данной главе сказано, что "если вы хотите сделать глобальную переменную внешней (которую можно использовать в любом файле программы) — используйте ключевое слово extern".. при этом ниже написано "По умолчанию, неконстантные переменные, объявленные вне блока, считаются внешними." Возникает недопонимание, обязательно ли в таком случаее вообще использовать extern?
    Более того в "Уроке №20. Многофайловые программы" приводится пример с вызовом функции из другого файла без extern, и в данной главе сказано, что "Функции имеют такие же свойства связи, что и переменные…", в связи с чем также складывается впечатление, что использование extern с глобальными переменными необязательно.

    1. Виталий:

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

  10. Алекс:

    Здарова!
    Уже не первый раз обращаюсь именно к этому уроку, с целью перечитать про глобальные переменные.
    Мне кажется было-бы круто, в название урока добавить например extern, через запятую после основного названия.
    Потому, что ищешь поиском по странице с содержанием и не находишь. Думаю я не первый и не последний такой :))
    А так все круто!

    PS: О! Ща куплю твою книгу!

    1. Фото аватара Юрий:

      Привет, чуть позже добавлю оглавление.

  11. Илья:

    Здравствуйте. Возник вопрос, в заголовке "Файловая область видимости vs глобальная область видимости" говорится, что такая переменная не будет использоваться во всех файлах. Почему?Она получила свое определение в одном файле, не константная, следовательно, имеет видимость во всех файлах. А в файле main.cpp она предварительно объявляется с помощью extern?

    Спасибо большое!

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

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