Определение глобальной константы в С++

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

  • #define GLOBAL_CONST_VAR 0xFF
  • int GLOBAL_CONST_VAR = 0xFF;
  • Некоторые функции сохраняют значение (например, int get_GLOBAL_CONST_VAR())
  • enum { GLOBAL_CONST_VAR = 0xFF; }
  • const int GLOBAL_CONST_VAR = 0xFF;
  • extern const int GLOBAL_CONST_VAR; и в одном исходном файле const int GLOBAL_CONST_VAR = 0xFF;

Вариант (1) - это определенно не тот вариант, который вы хотели бы использовать

Опция (2) - определение экземпляра переменной в каждом объектном файле с использованием файла заголовка

Вариант (3) - В большинстве случаев ИМО убивает

Опция (4) - во многих случаях может быть не очень хорошо, поскольку enum не имеет конкретного типа (С++ 0X добавит возможность определить тип)

Поэтому в большинстве случаев мне нужно выбирать между (5) и (6). Мои вопросы:

  • Что вы предпочитаете (5) или (6)?
  • Почему (5) нормально, а (2) - нет?

Ответ 1

(5) говорит именно то, что вы хотите сказать. Плюс это позволяет компилятору оптимизировать его большую часть времени. (6), с другой стороны, не позволит компилятору когда-либо оптимизировать его, потому что компилятор не знает, измените ли вы его в конечном итоге или нет.

Ответ 2

Определенно идти с опцией 5 - он безопасен и позволяет компилятору оптимизировать (не принимать адрес этой переменной:) Также, если он в заголовке - вставьте его в пространство имен, чтобы избежать загрязнения глобальной области:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;

Ответ 3

(5) "лучше", чем (6), потому что он определяет GLOBAL_CONST_VAR как Интегральное выражение константы (ICE) во всех единицах перевода. Например, вы сможете использовать его в качестве размера массива и в качестве метки регистра во всех единицах перевода. В случае (6) GLOBAL_CONST_VAR будет ICE только в той единице перевода, где она определена, и только после точки определения. В других единицах перевода это не будет работать как ICE.

Однако имейте в виду, что (5) дает внутреннюю связь GLOBAL_CONST_VAR, что означает, что "идентификация адреса" GLOBAL_CONST_VAR будет отличаться в каждой единице перевода, т.е. &GLOBAL_CONST_VAR даст вам различное значение указателя в каждой единице перевода. В большинстве случаев это не имеет значения, но если вам нужен постоянный объект, имеющий согласованную глобальную "идентификацию адреса", то вам придется пойти на (6), жертвуя ICE-значением константы в процесс.

Кроме того, когда ICE-значение константы не является проблемой (не является целочисленным типом), а размер типа увеличивается (не скалярным типом), тогда (6) обычно становится лучшим подходом, чем (5).

(2) не в порядке, потому что GLOBAL_CONST_VAR в (2) имеет внешнюю связь по умолчанию. Если вы поместите его в заголовочный файл, вы, как правило, получите несколько определений GLOBAL_CONST_VAR, что является ошибкой. Объекты const в C++ по умолчанию имеют внутреннюю связь, поэтому (5) работает (и именно поэтому, как я уже говорил выше, вы получаете отдельный, независимый GLOBAL_CONST_VAR в каждой единице перевода).


Начиная с C++ 17 у вас есть возможность объявить

inline extern const int GLOBAL_CONST_VAR = 0xFF;

в заголовочном файле. Это дает вам ICE во всех единицах перевода (точно так же, как и в методе (5)), в то же время поддерживая глобальную идентичность адреса GLOBAL_CONST_VAR - во всех единицах перевода он будет иметь один и тот же адрес.

Ответ 4

Если это будет константа, тогда вы должны пометить ее как константу - почему 2 плохо по моему мнению.

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

Выбор между 5 и 6 - хм; 5 мне просто лучше.

В 6) значение без необходимости отделяется от него.

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

Ответ 5

Если вы используете С++ 11 или более позднюю версию, попробуйте использовать константы времени компиляции:

constexpr int GLOBAL_CONST_VAR{ 0xff };

Ответ 6

Чтобы ответить на второй вопрос:

(2) является незаконным, поскольку он нарушает правило одного определения. Он определяет GLOBAL_CONST_VAR в каждом файле, где он включен, то есть более одного раза. (5) является законным, поскольку он не подпадает под действие правила Единого определения. Каждый GLOBAL_CONST_VAR является отдельным определением, локальным для того файла, в который он был включен. Все эти определения имеют одинаковое имя и ценность, но их адреса могут отличаться.

Ответ 7

const int GLOBAL_CONST_VAR = 0xFF;

потому что это константа!

Ответ 8

#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this

Ответ 9

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

(4) также является достойным выбором, если ваш приоритет гарантирует, что пространство памяти никогда не выделяется, но оно работает только для интегральных констант, конечно.

Ответ 10

C++ 17 inline переменные

Эта удивительная функция C++ 17 позволяет нам:

  • удобно использовать только один адрес памяти для каждой константы
  • сохранить его как constexpr: Как объявить constexpr extern?
  • сделать это в одной строке из одного заголовка

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Скомпилируйте и запустите:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream.

Смотрите также: Как работают встроенные переменные?

C++ стандарт для встроенных переменных

Стандарт C++ гарантирует, что адреса будут одинаковыми. C++ 17 N4659 стандартный черновик 10.1.6 "Встроенный спецификатор":

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

cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static не указан, то он имеет внешнюю связь.

Реализация встроенных переменных

Мы можем наблюдать, как это реализовано с помощью:

nm main.o notmain.o

который содержит:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

и man nm говорит о u:

"U" Символ является уникальным глобальным символом. Это расширение GNU для стандартного набора привязок символов ELF. Для такого символа динамический компоновщик будет следить за тем, чтобы во всем процессе                есть только один символ с этим именем и используемым типом.

поэтому мы видим, что для этого есть выделенное расширение ELF.

Протестировано на GCC 7.4.0, Ubuntu 18.04.