Как объявить глобальную переменную с
Перейти к содержимому

Как объявить глобальную переменную с

Как объявить глобальную переменную с

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

Переменные бывают глобальными и локальными или автоматическими.

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

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

Здесь переменная n является глобальной и доступна из любой функции. При этом любая функция может изменить ее значение.

Автоматические переменные

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

Здесь в функции printn определена автоматическая переменная n. В функции main определена автоматическая переменная a. При вне своих функций переменные недоступны. Например, мы не можем использовать переменную n в функции main, так как ее область видимости ограничена функцией printn.

К автоматическим переменным может применяться ключевое слово auto :

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

Сокрытие переменных

Автоматические переменные, определенные внутри блока кода, могут скрывать внешние переменные с тем же именем:

Здесь определено три переменных с именем n . Автоматическая переменная n, определенная на уровне функции main ( int n = 10; ) скрывает глобальную переменную n. А переменная n, определенная на уровне блока, скрывает переменную, определенную на уровне функции main.

Статические переменные

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

Например, пусть у нас будет функция со стандартной автоматической переменной:

Функция display вызывается три раза, и при каждом вызове программа повторно будет выделять память для переменной i, которая определена в функции. А после завершения работы display, память для переменной i будет освобождаться. Соответственно ее значение при каждом вызове будет неизменно:

Теперь сделаем переменную i статической:

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

Регистровые переменные

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

Ключевое слово register применяется к переменным:

И к параметрам функции:

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

Руководство Google по стилю в C++. Часть 3

Часть 1. Вступление
Часть 2. Заголовочные файлы
Часть 3. Область видимости
Часть 4. Классы

Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.

Область видимости

Пространство имён

Размещайте свой код в пространстве имён (за некоторыми исключениями). Пространство имён должно иметь уникальное имя, формируемое на основе названия проекта, и, возможно, пути. Не используйте директиву using (например, using namespace foo). Не используйте встроенные (inline) пространства имён. Для безымянных пространств имён смотрите Безымянные пространства имён и статические переменные.

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

За
Пространства имён позволяют избежать конфликта имён в больших программах, при этом сами имена остаются достаточно короткими.
Например, если два разных проекта содержат класс Foo в глобальной области видимости, имена могут конфликтовать. Если каждый проект размещает код в своё пространство имён, то project1::Foo и project2::Foo будут разными именами, конфликтов не будет, в то же время код каждого проекта будет использовать Foo без префикса.
Пространства имён inline автоматически делают видимыми свои имена для включающего пространства имён. Рассмотрим пример кода:

Здесь выражения outer::inner::foo() и outer::foo() взаимозаменяемы. Inline пространства имён в основном используются для ABI-совместимости разных версий.

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

  • Следуйте правилам Именования Пространств Имён.
  • В конце объявления пространства имён добавляйте комментарий, аналогично показанным в примерах.

Заключайте в пространство имён целиком файл с исходным кодом после #include-ов, объявлений/определений gflag-ов и предварительных объявлений классов из других пространств имён.

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

Не используйте using-директиву чтобы сделать доступными все имена из пространства имён.

Не используйте псевдонимы пространств имён в блоке namespace в заголовочном файле, за исключением явно обозначенных «внутренних» пространств имён. Связано это с тем, что любая декларация в заголовочном файле становится частью публичного API, экспортируемого этим файлом.

Безымянные пространства имён и статические переменные

Когда определения внутри .cc файла не используются в других исходных файлах, размещайте такие определения в безымянном пространстве имён или объявляйте их как static. Не используйте такой тип определения в заголовочных файлах (.h).

Определение
Размещённые в безымянном пространстве имён объявления могут быть слинкованы как internal (только для внутреннего использования). Функции и переменные также могут быть с internal линковкой, если они заявлены как static. Такие типы объявления подразумевают, что они будут недоступны из другого файла. Если другой файл объявляет сущность с таким же именем, то оба объявления будут полностью независимы.

Вердикт
Использование internal линковки в .cc файлах предпочтительно для любого кода, к которому не обращаются снаружи (из других файлов). Не используйте подходы internal линковки в .h файлах.
Формат описания безымянного пространства имён полностью аналогичен именованному варианту. Не забывайте к закрывающей скобке написать комментарий, в котором имя оставьте пустым:

Функции: глобальные, статические внутри класса, вне класса

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

За
Вообще функции (как статические, так и вне класса) довольно полезная штука, Кэп. И размещение функций либо в классе, либо в пространстве имён позволяет всё остальное содержать в чистоте (я про глобальное пространство имён).

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

Вердикт
Иногда полезно объявить функцию, не привязанную к экземпляру класса. И можно сделать либо статическую функцию в классе, либо внешнюю (вне класса) функцию. Желательно, чтобы функция-вне-класса не использовала внешних переменных и находилась в пространстве имён. Не создавайте классы только для группировки статических функций: это всё равно, что дать функциям некий префикс и группировка становится лишней.
Если требуется определить функцию: в .cc-файле; вне класса; используемой только в локальном файле — используйте internal линковку для ограничения области видимости.

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

Объявляйте переменные внутри функции в наиболее узкойобласти видимости, инициализируйте такие переменные при объявлении.
Язык C++ позволяет объявлять переменные в любом месте функции. Однако рекомендуется делать это в наиболее узкой (наиболее вложенной) области видимости, и по возможности ближе к первому использованию. Это облегчает поиск объявлений, проще узнать тип переменной и её начальное значение. Также рекомендуется использовать инициализацию, а не объявление с присваиванием. Примеры:

Переменные, необходимые только внутри кода if, while и for лучше объявлять внутри условий, тогда область их видимости будет ограничена только соответствующим блоком кода:

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

Возможно было бы более эффективно такую переменную (которая используется внутри цикла) объявить вне цикла:

Переменные: статические и глобальные

Объекты в статической области видимости/действия запрещены, кроме тривиально удаляемых. Фактически это означает, что деструктор должен ничего не делать (включая вложенные или базовые типы). Формально это можно описать, что тип не содержит пользовательского или виртуального деструктора и что все базовые типы и не-статические члены ведут себя аналогично (т.е. являются тривиально удаляемыми). Статические переменные в функциях могут быть динамически инициализированными. Использование же динамической инициализации для статических членов класса или переменных в области пространства имён (namespace) в целом не рекомендуется, однако допустимо в ряде случаев (см. ниже).
Эмпирическое правило: если глобальную переменную (рассматривая её изолированно) можно объявить как constexpr, значить она соответствует вышеуказанным требованиям.

Определение
Каждый объект имеет тот или иной тип времени жизни / storage duration, и, очевидно, это влияет на время жизни объекта. Объекты статического типа доступны с момента их инициализации до момента завершения программы. Такие объекты могут быть переменными в пространстве имён («глобальные переменные»), статическими членами классов, локальными переменными внутри функций со спецификатором static. Статические переменные в функциях инициализируются, когда поток выполнения кода проходит в первый раз через объявление; все остальные объекты статического типа инициализируются в фазе старта (start-up) приложения. Все объекты статического типа удаляются в фазе завершения программы (до обработки незавершённых(unjoined) потоков).
Инициализация может быть динамическая, т.е. во время инициализации делается что-то нетривиальное: например, конструктор выделяет память, или переменная инициализируется идентификатором процесса. Также инициализации может быть статической. Сначала выполняется статическая инициализация: для всех объектов статического типа (объект инициализируется либо заданной константой, либо заполняется нулями). Далее, если необходимо, выполняется динамическая инициализация.

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

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

Вердикт
Когда деструктор тривиальный, тогда порядок разрушения в принципе не важен. В противном случае есть риск обратиться к объекту после его разрушения. Поэтому, настоятельно рекомендуется использовать только переменные со статическим типом размещения (конечно, если они имеют тривиальный деструктор). Фундаментальные типы (указатели или int), как и массивы из них, являются тривиально разрушаемыми. Переменные с типом constexp также тривиально разрушаемые.

Отметим, что ссылка не есть сам объект, и, следовательно, к ним не применяются ограничения по разрушению объекта. Хотя ограничения на динамическую инициализацию остаются в силе. В частности, внутри функции допустим следующий код static T& t = *new T;.

Тонкости инициализации

Инициализация может быть запутанной: мало того, что конструктору нужно (желательно правильно) отработать, так есть ещё и предварительные вычисления:

На выполнение всех выражений, кроме первого, может повлиять порядок инициализации, который может быть разным/неопределённым (или зависимым от . ).
Рассмотрим константную инициализацию. Это означает, что инициализационное выражение — константное, и если при создании объекта вызывается конструктор, то он (конструктор) тоже должен быть заявлен как constexpr:

Константная инициализация является рекомендуемой для большинства случаев. Константную инициализацию переменных со статическим размещением рекомендуется помечать как constexpr или атрибутом ABSL_CONST_INIT
. Любую переменную вне функции, со статическим размещением и без указанной выше маркировки следует считать динамически инициализируемой (и тщательно проверять на ревью кода).

Например, следующие инициализации могут привести к проблемам:

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

Динамическая же инициализация статических переменных в функциях (локальных) допустима и является широко распространённой практикой.

Стандартные практики
  • Глобальные строки: если требуется глобальная или статическая строковая константа, то рекомендуется использовать простой символьный массив или указатель на первый символ строкового литерала. Строковые литералы обычно находятся в статическом размещении (их время жизни) и этого в большинстве случаев достаточно.
  • Динамические контейнеры (map, set и т.д.): если требуется статическая коллекция с фиксированными данными (например, таблицы значений для поиска), то не используйте динамические контейнеры из стандартной библиотеки как тип для статической переменной, т.к. у этих контейнеров нетривиальный деструктор. Вместо этого попробуйте использовать массивы простых (тривиальных) типов, например массив из массивов целых чисел (вместо std::map<int, int>) или, например, массив структур с полями int и const char*. Учтите, что для небольших коллекций линейный поиск обычно вполне приемлем (и может быть очень эффективным благодаря компактному размещению в памяти). Также можете воспользоваться алгоритмами absl/algorithm/container.h для стандартных операций. Также возможно создавать коллекцию данных уже отсортированной и использовать алгоритм бинарного поиска. Если без динамического контейнера не обойтись, то попробуйте использовать статическую переменную-указатель, объявленную в функции (см. ниже).
  • Умные указатели (unique_ptr, shared_ptr): умные указатели освобождают ресурсы в деструкторе и поэтому использовать их нельзя. Попробуйте применить другие практики/способы, описанные в разделе. Например, одно из простых решений это использовать обычный указатель на динамически выделенный объект и далее никогда не удалять его (см. последний вариант списка).
  • Статические переменные пользовательского типа: если требуется статический и константный пользовательский тип, заполненный данными, то можете объявить у этого типа тривиальный деструктор и constexpr конструктор.
  • Если все другие способы не подходят, то можно создать динамический объект и никогда не удалять его. Объект можно создать с использованием статического указателя или ссылки, объявленной в функции, например: static const auto& impl = *new T(args. );.

Потоковые переменные

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

Определение
Начиная с C++11 переменные можно объявлять со спецификатором thread_local:

Каждая такая переменная представляется собой коллекцию объектов. Разные потоки работают с разными экземплярами переменной (каждый со своим экземпляром). По поведению переменные thread_local во многом похожи на Переменные со статическим типом размещения. Например, они могут быть объявлены в пространстве имён, внутри функций, как статические члены класса (как обычные члены класса — нельзя).
Инициализация потоковых переменных очень напоминает статические переменные, за исключением что это делается для каждого потока. В том числе это означает, что безопасно объявлять thread_local переменные внутри функции. Однако в целом thread_local переменные подвержены тем же проблемам, что и статические переменные (различный порядок инициализации и т.д.).
Переменная thread_local разрушается вместе с завершением потока, так что здесь нет проблем с порядком разрушения, как у статических переменных.

  • Потоковые переменные в принципе защищены от эффекта гонок (т.к. каждый поток обычно имеет доступ только к своему экземпляру), что очень полезно для программирования потоков.
  • Переменные thread_local являются единственным стандартизованным средством для создания локальных потоковых данных.
  • Операции с thread_local переменными могут привести к выполнению кода, который не предполагался и его размер также может быть различным.
  • Переменные thread_local являются по сути глобальными переменными со всеми их недостатками (за исключением потокобезопасности).
  • Выделяемая для thread_local память зависит от количества потоков и её размер может быть значительным.
  • Обыкновенный член класса не может быть thread_local.
  • В ряде случаев thread_local переменные могут проигрывать по эффективности хорошо оптимизированному компилятором коду (например, интринсикам/intrinsics).

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

Переменные thread_local в классе или пространстве имён необходимо инициализировать константой времени компиляции (т.е. динамическая инициализация недопустима). Для этого необходимо аннотировать эти thread_local переменные с помощью ABSL_CONST_INIT
(или constexpr, но это лучше использовать пореже):

Переменные thread_local должны быть предпочтительным способом определения потоковых данных.

Область видимости объектов

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

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

Время жизни объекта может быть глобальным и локальным.

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

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

Область видимости локальной переменной k — функция autofunc() . Каждый раз при входе в функцию с идентификатором k ассоциируется некоторая ячейка памяти, в которую помещается значение равное 1.

Результат выполнения программы
Локальная переменная k
Та же программа, но с использованием глобального объекта

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

Результат выполнения
Использование глобальных переменных

Модификация объектов

Модификация или видоизменение объектов в языке Си применяется для того, чтобы изменить диапазон значений или область действия объекта. Ключевые слова, которые применяются для модификации, называются модификаторами .

Модификатор unsigned предназначен для того, чтобы объявлять беззнаковую целочисленную переменную, тем самым изменив диапазон представления этой переменной.

Модификатор extern предназначен для использования в данном программном модуле объекта, который объявлен в другом программном модуле.

Пример окна проекта, состоящего из двух файлов
Модификатор extern

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

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

Переменная k в функции autofunc() зафиксирована в оперативной памяти. Инициализация k проводится только один раз — при первом вызове функции. При повторном обращении к функции autofunc() инициализация переменной k не будет производиться. Значение переменной k и ее адрес сохраняются в оперативной памяти, однако эта переменная не будет доступна из других функций.

Результат выполнения программы
Статическая переменная k

Модификатор register предназначен для того, чтобы поместить переменную в один из регистров общего назначения центрального процессора при наличии свободного регистра. Благодаря этому повышается скорость работы с данными. Это необходимо для создания управляющих программ, где требуется высокая скорость обработки данных.

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

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