Определение глобальной константы в C ++ - PullRequest
74 голосов
/ 15 февраля 2010

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

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Некоторая функция возвращает значение (например, int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; и в одном исходном файле const int GLOBAL_CONST_VAR = 0xFF;

Option (1) - это определенно не та опция, которую вы хотели бы использовать

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

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

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

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

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

Ответы [ 10 ]

64 голосов
/ 15 февраля 2010

Обязательно используйте вариант 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;
29 голосов
/ 15 февраля 2010

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

22 голосов
/ 17 февраля 2010

(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 в каждой единице перевода).

5 голосов
/ 15 февраля 2010

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

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

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

В 6) значение неоправданно отделено от его объявления.

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

4 голосов
/ 15 сентября 2018

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

constexpr int GLOBAL_CONST_VAR{ 0xff };
4 голосов
/ 16 февраля 2010

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

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

2 голосов
/ 15 февраля 2010
const int GLOBAL_CONST_VAR = 0xFF;

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

1 голос
/ 16 февраля 2010

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

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

1 голос
/ 16 февраля 2010
#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
0 голосов

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...