Смешивание extern и const

Могу ли я смешивать extern и const как extern const? Если да, то определяет ли квалификатор const его господство только в пределах области, в которой он объявлен, или он должен точно соответствовать объявлению единицы перевода, в которой он объявлен? Т.е. могу ли я объявить say extern const int i;, даже если фактическое i не является константой, и наоборот?


person legends2k    schedule 03.02.2010    source источник
comment
Этот вопрос не касается непосредственно вашего, но содержит всю необходимую информацию: stackoverflow.com/questions/2151831/non-integral-constants/   -  person GManNickG    schedule 03.02.2010
comment
Позвольте мне упомянуть разницу в связывании здесь: использование extern с const отключит сворачивание констант и заставит компилятор выделить память для константы, чего не было бы в противном случае, при этом он будет выполнять замену на месте (после сворачивания , если возможно). [следовательно, это не рекомендуется, и я решил не использовать его :)]   -  person legends2k    schedule 20.02.2010
comment
если это константа, зачем компилятору отключать сворачивание констант в случае extern?   -  person Jimm    schedule 12.03.2012
comment
@Jimm: Потому что при объявлении extern const вы не указываете значение инициализации (см. Принятый ответ), и компилятор будет ожидать, что компоновщик заполнит пробелы и тем самым вынудит компоновщик выделить место для константы.   -  person legends2k    schedule 16.10.2012


Ответы (5)


Обычный паттерн:

  • file.h:
    extern const int a_global_var;
  • file.c:
    #include "file.h"
    const int a_global_var = /* some const expression */;

Изменить: включенный комментарий legends2k. Спасибо.

person edgar.holleis    schedule 03.02.2010
comment
Вы имели в виду const int a_global_var = <some_value_here>;? - person legends2k; 03.02.2010
comment
Я не мог понять преимущества extern const, и тогда мне пришло в голову, что это сэкономит большое время на компиляции при изменении констант. Спасибо - person doron; 03.02.2010
comment
Гм, поскольку const s неявно static, вам нужен extern даже в определении a_global_varfile.c). Без этого все, что включает file.h, не будет ссылаться, потому что ищет const int a_global_var с внешней связью. - person ; 25.03.2010
comment
Стандарт C ++ 11 3.5 / 3 (выделено мной): Имя ... имеет внутреннюю связь, если это имя переменной, которая явно объявлена ​​const или constexpr и не объявлена ​​явно extern и не объявлена ​​ранее иметь внешнюю связь; - person legends2k; 22.07.2013
comment
@ user123456: Нет, extern не существует - это правильно. Если константа предварительно объявлена ​​как extern, то extern в определении не является обязательным. Даже без явного extern он определит такой константный объект с внешней связью. - stackoverflow.com/a/2151876/183120 - person legends2k; 22.07.2013
comment
Однако, если file.c не включает file.h, определение должно включать extern - person patentfox; 30.05.2015
comment
Переменная const не может неявно отображаться в других файлах, если не добавить явный extern. поэтому file.c должен быть extern const int a_global_var = /* expression */; - person expoter; 08.03.2016
comment
Исходная ссылка на Underhanded C Programming Contest теперь мертва - и я не смог найти ссылку на Архивы Wayback Machine изначально связаны с сайтом. 8- ( - person SlySven; 12.05.2017
comment
Обычно компилятор C ++ избегает создания хранилища для константы, а вместо этого сохраняет определение в своей таблице символов. Однако, когда вы используете extern с константой, вы принудительно выделяете память (это также верно для некоторых других случаев, таких как получение адреса константы). Хранилище должно быть выделено, потому что extern говорит использовать внешнюю связь, что означает, что несколько единиц перевода должны иметь возможность ссылаться на элемент, что требует, чтобы у него было хранилище. - person ShowLove; 26.03.2020

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

Если вы думаете, что хотите extern const, то более вероятно, что вы действительно захотите использовать C ++ 17 встроенных переменных.

Эта замечательная функция 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 ++ гарантирует, что адреса будут такими же. Стандартный черновик 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.

До C ++ 17: extern const

extern const работает, как в примере ниже, но у inline есть недостатки:

  • невозможно создать переменную constexpr с помощью этого метода, только inline позволяет это: Как объявить constexpr extern?
  • это менее элегантно, поскольку вам нужно объявлять и определять переменную отдельно в заголовке и файле cpp

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.cpp

#include "notmain.hpp"

const int notmain_i = 42;

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

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

вверх по течению на GitHub.

Альтернативы только для заголовков до C ++ 17

Они не так хороши, как решение extern, но работают и занимают только одну ячейку памяти:

Функция constexpr, потому что constexpr подразумевает inline и inline позволяет (принудительно) отображать определение во всех единицах перевода:

constexpr int shared_inline_constexpr() { return 42; }

и я уверен, что любой достойный компилятор встроит вызов.

Вы также можете использовать статическую целочисленную переменную const или constexpr, например:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

но вы не можете делать такие вещи, как получение его адреса, иначе он станет использоваться odr, см. также: https://en.cppreference.com/w/cpp/language/static" Постоянные статические члены "и Определение статических элементов данных constexpr

Есть ли способ полностью встроить его?

TODO: есть ли способ полностью встроить переменную без использования памяти?

Очень похоже на то, что делает препроцессор.

Это потребует как-то:

  • запрещение или обнаружение, если адрес переменной взят
  • добавить эту информацию в объектные файлы ELF и позволить LTO оптимизировать ее

Связанный:

Протестировано в Ubuntu 18.10, GCC 8.2.0.

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 22.12.2018
comment
Спасибо за этот ответ. Следует отметить, что статическая переменная-член (но не переменная области пространства имен), объявленная constexpr, неявно является встроенной переменной. . - person legends2k; 11.08.2019
comment
@ legends2k, да, это еще один возможный обходной путь, я упомянул об этом по адресу: stackoverflow.com/questions/38043442/ также добавит сюда. - person Ciro Santilli 新疆再教育营六四事件ۍ 11.08.2019
comment
inline имеет проблему, заключающуюся в том, что если другая DLL уже была связана с вашей DLL, и вы изменили только это значение, чем перестроили свою DLL, старая DLL продолжит использовать старое значение. extern const решает эту проблему, но требует фактического просмотра памяти каждый раз, когда один из ваших ссылочных клиентов запрашивает значение. - person Keith Russell; 19.11.2019

Вы можете использовать их вместе. Но вам нужно быть последовательным в использовании const, потому что, когда C ++ выполняет оформление имен, const включается в информацию о типе, которая используется для украшения имен символов. поэтому extern const int i будет относиться к другой переменной, чем extern int i

Если вы не используете extern "C" {}. Оформление имени C не обращает внимания на const.

person John Knoeller    schedule 03.02.2010

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

На Possibility.com есть достойная статья с дополнительной информацией.

person Jon Cage    schedule 03.02.2010

Да, вы можете использовать их вместе.

Если вы объявите «extern const int i», то i будет const во всей своей области видимости. Переопределить его как неконстантное невозможно. Конечно, вы можете обойти флаг const, отбросив его (используя const_cast).

person Dimitri C.    schedule 03.02.2010