Визначення глобальної константи в C ++


81

Я хочу визначити константу в 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;

Варіант (1) - це точно не той варіант, який ви хотіли б використовувати

Варіант (2) - визначення екземпляра змінної в кожному об'єктному файлі за допомогою заголовного файлу

Варіант (3) - ІМО в більшості випадків перестає вбивати

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

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

  1. Що ви віддаєте перевагу (5) чи (6)?
  2. Чому (5) - нормально, а (2) - ні?

1
5 проти 2: "const" означає внутрішній зв'язок. Коли ви включаєте цей заголовок версії-5 до декількох одиниць перекладу, ви не порушуватимете "правила одного визначення". Крім того, const дозволяє компілятору робити "постійне згортання", тоді як значення змінної non-const може змінюватися. Варіант 6 помилковий. Вам також потрібно "extern" у файлі cpp, щоб примусити зовнішні зв'язки, інакше ви отримаєте помилки компонувальника. Варіант 6 має перевагу приховування вартості. Але це також унеможливлює постійне згортання.
sellibitze

Відповіді:


32

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


1
OTOH, 5 є технічно незаконним як порушення ОРС. Однак більшість компіляторів ігноруватимуть це.
Джоел

Е-е, я вважаю за краще думати про це як про те, що взагалі нічого не визначив, я просто сказав компілятору дати гарне ім'я номеру. Для всіх намірів і цілей ось що є (5), що означає відсутність накладних витрат під час виконання.
Сліпий

2
Чи є (5) порушенням ОРС? Якщо це так, то (6) є кращим. Чому компілятор "не знає, чи зміниш ти це" у випадку (6)? extern const int ...і const int ...обидва постійні, чи не так?
D.Shawley

4
AFAIK, між 5) та 6), допускається лише 6), коли тип константи не заснований на int.
Klaim

11
Порушення ODR немає, константні об’єкти за замовчуванням статичні.
авакар

71

Безумовно, скористайтеся варіантом 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
Я отримую помилку перевизначення при спробі включити header.hppдо кількох вихідних файлів.
LRDPRDX

Не впевнений, чому це питання все ще голосує - минуло майже десять років, але сьогодні ми створили constexprі набрали переліки подібних речей.
Микола Фетисов

24

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

Однак майте на увазі, що (5) забезпечує GLOBAL_CONST_VARвнутрішній зв'язок, що означає, що "ідентичність адреси" GLOBAL_CONST_VARбуде відрізнятися в кожній одиниці перекладу,&GLOBAL_CONST_VAR дасть вам різне значення покажчика в кожній одиниці перекладу. У більшості випадків використання це не має значення, але якщо вам потрібен константний об'єкт, який має послідовну глобальну "ідентичність адреси", то вам доведеться піти з (6), жертвуючи ДВГ-константою константи в процес.

Крім того, коли ДВС константи не є проблемою (не інтегральним типом), а розмір типу збільшується (не скалярний тип), тоді (6) зазвичай стає кращим підходом, ніж (5).

(2) не в порядку, оскільки GLOBAL_CONST_VARin (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- у всіх одиницях перекладу він матиме однакову адресу.


8

Якщо ви використовуєте C ++ 11 або пізнішу версію, спробуйте використати константи часу компіляції:

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
ІМХО, це єдине задовільне рішення цієї проблеми.
lanoxx

5

Якщо це буде константа, тоді ви повинні позначити її як константу - ось чому 2, на мій погляд, погано.

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

Вибір між 5 і 6 - ммм; 5 мені просто легше.

У 6) значення непотрібно відокремлюється від його декларації.

Як правило, у мене буде один або кілька із цих заголовків, які визначають лише константи і т.д. всередині них, а потім жодних інших «розумних» речей - приємних легких заголовків, які легко можна включити де завгодно.


3
(6) не є непотрібною відстороненістю, це навмисний вибір. Якщо у вас багато великих констант, ви витратите багато місця у виконуваному файлі, якщо не оголосите їх як у (6). Це може траплятися в математичних бібліотеках ... відходів може бути менше 100 тис., Але навіть це часом важливо. (Деякі компілятори мають інші способи обійти це, я думаю, MSVC має атрибут "раз" або щось подібне.)
Ден Олсон,

За допомогою (5) ви не можете бути впевнені, що це залишиться const (ви завжди можете відкинути constness). Ось чому я все-таки віддав би перевагу типу enum.
fmuecke

@Dan Olson - це дуже хороший момент - моя відповідь базувалася на тому, що тут йдеться про тип int; але коли йдеться про великі значення, зовнішнє оголошення - це справді кращий план.
Андраш Золтан,

@fmuecke - Так, ти маєш рацію - у цьому випадку значення переліку перешкоджає цьому. Але чи означає це, що ми завжди повинні захищати свої цінності від записів таким чином? Якщо програміст хоче зловживати кодом, існує так багато областей, де приведення (target_type *) ((void *) & value) може спричинити хаос, що ми не можемо зловити, що іноді нам просто потрібно довіряти їм; і справді ми самі, ні?
Андраш Золтан,

@fmuecke Змінна, яка оголошена const, не може бути змінена програмою (спроба зробити це невизначена поведінка). const_cast визначається лише в ситуаціях, коли вихідна змінна не була оголошена const (наприклад, передача значення, яке не є const, у функцію як const &).
Девід Стоун

5

Щоб відповісти на ваше друге запитання:

(2) є незаконним, оскільки порушує правило Єдиного визначення. Він визначає GLOBAL_CONST_VARу кожному файлі, куди він включений, тобто більше одного разу. (5) є законним, оскільки на нього не поширюється правило Єдиного визначення. Кожне GLOBAL_CONST_VAR- це окреме визначення, локальне для того файлу, куди воно включено. Звичайно, усі ці визначення мають однакову назву та значення, але їх адреси можуть відрізнятися.


4

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 вгору за течією .

Дивіться також: Як працюють вбудовані змінні?

Стандарт 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.


2
const int GLOBAL_CONST_VAR = 0xFF;

тому що це константа!


1
І це не буде розглядатися як макрос, що полегшить налагодження за допомогою нього.
kayleeFrye_onDeck

-1, Це призведе до перевизначення попереджень / помилок при включенні заголовка до декількох вихідних файлів. Також ця відповідь є дублікатом відповіді Миколи Фетисова.
lanoxx

1

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

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


1
#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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.