Форум программистов, компьютерный форум CyberForum.ru

C++

Войти
Регистрация
Восстановить пароль
 
Kastaneda
Форумчанин
Эксперт С++
4479 / 2841 / 226
Регистрация: 12.12.2009
Сообщений: 7,222
Записей в блоге: 1
Завершенные тесты: 1
#1

Как компилятор обрабатывает член класса static constexpr const char* - C++

16.09.2016, 12:05. Просмотров 604. Ответов 14
Метки нет (Все метки)

Привет!

Наткнулся на непонятный момент
C++
1
2
3
4
5
class Foo
{
public:
    static constexpr const char* PTR = "value";
};
без constexpr не компилируется.
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
gray_fox
What a waste!
1411 / 1140 / 55
Регистрация: 21.04.2012
Сообщений: 2,362
Завершенные тесты: 3
16.09.2016, 22:28     Как компилятор обрабатывает член класса static constexpr const char* #2
Kastaneda, судя по сообщениям компилятора, const char* - не "интегральный" тип, и видимо в этом загвоздка - совмещать объявление статического члена класса и его инициализацию можно только для "интегральных" типов; в то время как при использовании constexpr инициализация необходима при объявлении.
DrOffset
6909 / 4102 / 933
Регистрация: 30.01.2014
Сообщений: 6,899
17.09.2016, 00:17     Как компилятор обрабатывает член класса static constexpr const char* #3
Цитата Сообщение от Kastaneda Посмотреть сообщение
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
Если понадобится адрес переменной, то потребуется определение (без него будет ошибка линкера). До этих пор линкер вообще не задействуется - это compile-time константа.
Kastaneda
Форумчанин
Эксперт С++
4479 / 2841 / 226
Регистрация: 12.12.2009
Сообщений: 7,222
Записей в блоге: 1
Завершенные тесты: 1
17.09.2016, 05:25  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #4
Цитата Сообщение от DrOffset Посмотреть сообщение
До этих пор линкер вообще не задействуется - это compile-time константа.
да, это я понимаю. Но ведь эта константа содержит адрес строки, который становится известным на этапе линковки, как компилятор обходится без линкера?
ct0r
Игогошка!
1768 / 670 / 42
Регистрация: 19.08.2012
Сообщений: 1,284
Завершенные тесты: 1
17.09.2016, 14:33     Как компилятор обрабатывает член класса static constexpr const char* #5
Цитата Сообщение от Kastaneda Посмотреть сообщение
Но ведь эта константа содержит адрес строки, который становится известным на этапе линковки, как компилятор обходится без линкера?
1. Эта штука называется address constant expression.
2. А зачем ему линкер? Для таких объектов у компилятора адрес - это просто какая-то релокация. Он ее потом отправит в elf и все. А линкер дальше будет с ее использованием по соответствующему выражению адрес считать. Компилятору же нужно только знать, что эта релокация - константа.
DrOffset
6909 / 4102 / 933
Регистрация: 30.01.2014
Сообщений: 6,899
17.09.2016, 18:16     Как компилятор обрабатывает член класса static constexpr const char* #6
Цитата Сообщение от Kastaneda Посмотреть сообщение
как компилятор обходится без линкера?
Точно также, как он обходится без него, когда ты передаешь адрес функции в параметр шаблона.
Kastaneda
Форумчанин
Эксперт С++
4479 / 2841 / 226
Регистрация: 12.12.2009
Сообщений: 7,222
Записей в блоге: 1
Завершенные тесты: 1
18.09.2016, 08:45  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #7
Цитата Сообщение от ct0r Посмотреть сообщение
Эта штука называется address constant expression
Во, спасибо, знаний вот этого термина мне не хватало.

Цитата Сообщение от DrOffset Посмотреть сообщение
Точно также, как он обходится без него, когда ты передаешь адрес функции в параметр шаблона.
Да, логично, похоже разобрались
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3635 / 1910 / 503
Регистрация: 18.10.2014
Сообщений: 3,476
20.09.2016, 06:14     Как компилятор обрабатывает член класса static constexpr const char* #8
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от Kastaneda Посмотреть сообщение
Вопрос - как компилятора без линкера разруливает это дело, ведь constexpr value ему нужен на этапе компиляции? Если он это пережевывает, почему тогда constexpr необходим?
Концепция адресной константы была еще в языке С с самого начала стандартизованных времен. Про этом язык С никогда не поддерживал динамической инициализации для объектов со статическим storage duration. Т.е. при старте программы на C все объекты со статическим storage duration должны "родиться" уже проинициализированными, в том числе такие как

C
1
2
static int i;
static int *pi = &i;
Понятно, что значение pi не может быть известным на стадии компиляции. Более того, это значение даже в те времена в Unix не обязательно было известно даже на стадии линковки, а могло оказаться известным только на стадии загрузки (например, для глобальных объектов, определенных в shared objects). То есть конкретное значение адресной константы в общем случае может быть даже не линкером, а только загрузчиком в момент начала выполнения.

По этой причине спецификация адресных констант в С была специально подогнана под возможности загрузчиков. А именно, в С адресные константы выступают как константы только в инициализаторах и только либо сами собой, либо с прибавлением к адресной константе (или вычитанием из нее) какого-то смещения, известного на стадии компиляции (что разрешает в т.ч. применение операторов [] и -> в комбинации с & для формирования новых адресных констант). Другими словами, адресные константы в С могут быть использованы как константы только для целей относительной адресации.

То есть в рамках спецификации, нельзя написать

C
1
static int array[(size_t) pi % 100];
ибо, понятое дело, это требует знания точного значения (size_t) pi на стадии компиляции.

Так вот спецификация constexpr в С++ совсем не далеко уходит от этих ограничений, пришедших в С++ из С. Фактически, все, чего вы добиваетесь указанием constexpr в этом объявлении указателя, это превращение в constexpr таких выражений, как PTR[5]. Для этого инициализатор PTR должен быть виден компилятору, т.е. присутствовать в объявлении. Ясно, что для определения значения PTR[5] в таком случае компилятору совсем не нужно знать конкретного значения PTR.

Любые же попытки "выковырять" из PTR его численное значение на стадии компиляции (или как-то еще завязаться на абсолютную точку в памяти, куда он указывает) обречены на провал, даже несмотря на то, что PTR объявлен как constexpr.
Evg
Эксперт CАвтор FAQ
17462 / 5700 / 361
Регистрация: 30.03.2009
Сообщений: 15,648
Записей в блоге: 26
20.09.2016, 09:26     Как компилятор обрабатывает член класса static constexpr const char* #9
Если вернуться к изначальному вопросу. Почему без constexpr не компилируется - уже ответили: инициализатор должен быть не в теле класса, а там, где объявлено само тело статического поля. Т.е. где-то вне тела класса должно быть написано

C++
const char* Foo::PTR = "value";
Из объяснений TheCalligrapher я так понял, что для статической инициализации указателя constexpr ничего принципиально нового не вносит (во всяком случае на низком уровне). А почему наличие constexpr позволяет инициализатор писать в теле класса? На всякий случай: мне непонятна только языковая часть, как работает компилятор, линкер, загрузчик и прочее на низком уровне, мне хорошо известно
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3635 / 1910 / 503
Регистрация: 18.10.2014
Сообщений: 3,476
20.09.2016, 09:46     Как компилятор обрабатывает член класса static constexpr const char* #10
Цитата Сообщение от Evg Посмотреть сообщение
А почему наличие constexpr позволяет инициализатор писать в теле класса?
Для всех static constexpr разрешается писать инициализатор прямо в теле класса. Это необходимо для того, чтобы реализовать вычисления на стадии компиляции. В вычислениях на стадии компиляции и заключается вся идея constexpr.

В данном случае инициализатор необходим для того, чтобы на стадии компиляции вычислять выражения вида PTR[5].

При этом определением указателя PTR такое объявление не становится, даже несмотря на наличие инициализатора. Если где-то в коде PTR окажется использованным как lvalue, то придется его еще где-то определить. Инициализатор в определении повторять не надо.
Evg
Эксперт CАвтор FAQ
17462 / 5700 / 361
Регистрация: 30.03.2009
Сообщений: 15,648
Записей в блоге: 26
20.09.2016, 15:24     Как компилятор обрабатывает член класса static constexpr const char* #11
Я правильно понимаю ситуацию? Сначала (условно в C++98) разрешили писать инициализатор в теле класса ради того, чтобы static-поле класса можно было, грубо говоря, использовать в качестве размерности НЕавтоматического массива. Для указателей подобной необходимости не было, а потому не разрешали. Теперь (с C++11) из-за constexpr появилась необходимость использовать указатель (как rvalue) в других constexpr выражениях в НЕавтоматических местах, растущих от других constexpr'ов. Типа того?
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3635 / 1910 / 503
Регистрация: 18.10.2014
Сообщений: 3,476
21.09.2016, 08:34     Как компилятор обрабатывает член класса static constexpr const char* #12
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от Evg Посмотреть сообщение
Я правильно понимаю ситуацию? Сначала (условно в C++98) разрешили писать инициализатор в теле класса ради того, чтобы static-поле класса можно было, грубо говоря, использовать в качестве размерности НЕавтоматического массива. Для указателей подобной необходимости не было, а потому не разрешали. Теперь (с C++11) из-за constexpr появилась необходимость использовать указатель (как rvalue) в других constexpr выражениях в НЕавтоматических местах, растущих от других constexpr'ов. Типа того?
Ну да. Типа того.

В С++98 (и С++03) возможность использования static const членов класса в качестве констант времени компиляции понимали (отсюда разрешение на указание инициализаторов для целых и enum типов), но это возможность все равно рассматривалась, как "вторичная". Например, правила ODR в С++98 формально требовали отдельного определения такого члена класса, если он использовался (used) в программе. При этом не придавалось никакого значения тому, как он использовался. Даже если static const использовался только как rvalue (напр. только для задания размера массива), все равно наличие определения безусловно требовалось спецификацией языка. Хотя понятно, что с точки зрения здравого смысла такое определение было никому не нужно. (Многие компиляторы натуральным образом не следили за соблюдением этого требования, если такая константа использовалась только как rvalue).

В С++11 возможность использования таких констант именно и только как констант времени компиляции уже вышла на первый план. Правила ODR были модифицированы и требуют отдельного определения static const членов класса только если они odr-используются (odr-used) в программе, т.е. если они где-то используются именно как lvalue. А если такая константа используется только как rvalue, то делать для нее отдельное определение больше не требуется.

Но дальше расширять функциональность static const не стали (возможно, там скрыты какие-то конфликты), т.е. указывать инициализаторы для static const прямо в классе по-прежнему можно только для целых и enum типов. Вместо этого для создания констант времени компиляции ввели совершенно новый механизм constexpr. Для constexpr констант указывать инициализаторы прямо в классе можно всегда, независимо от типа. Понятно, что наличие видимых отовсюду инициализаторов совершенно необходимо для того, чтобы обрабатывать такие константы на стадии компиляции.
Evg
Эксперт CАвтор FAQ
17462 / 5700 / 361
Регистрация: 30.03.2009
Сообщений: 15,648
Записей в блоге: 26
21.09.2016, 16:12     Как компилятор обрабатывает член класса static constexpr const char* #13
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Даже если static const использовался только как rvalue (напр. только для задания размера массива), все равно наличие определения безусловно требовалось спецификацией языка. Хотя понятно, что с точки зрения здравого смысла такое определение было никому не нужно. (Многие компиляторы натуральным образом не следили за соблюдением этого требования, если такая константа использовалась только как rvalue)
Тут проблема немного другого уровня. Компилятор в общем-то не в состоянии проконтролировать, что определение неиспользуемой как value переменной где-то присутствует. Т.е. когда переменная используется (например, на неё взяли адрес), а определения нигде нет, то оно сломается на линковке. А когда переменная нигде не используется (все использования были заменены на константы), то очень сложно предпринимать усилия для того, чтобы сломаться в том случае, если определение переменной ни в одном из модулей не присутствует. Только путём построения избыточного кода или каких-то читерских вещей
Kastaneda
Форумчанин
Эксперт С++
4479 / 2841 / 226
Регистрация: 12.12.2009
Сообщений: 7,222
Записей в блоге: 1
Завершенные тесты: 1
22.09.2016, 06:54  [ТС]     Как компилятор обрабатывает член класса static constexpr const char* #14
После этого
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Фактически, все, чего вы добиваетесь указанием constexpr в этом объявлении указателя, это превращение в constexpr таких выражений, как PTR[5]. Для этого инициализатор PTR должен быть виден компилятору, т.е. присутствовать в объявлении. Ясно, что для определения значения PTR[5] в таком случае компилятору совсем не нужно знать конкретного значения PTR
окончательно все встало на свои места, спасибо.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char*
Еще ссылки по теме:
Копировать пустую строку в const char* Конструктор структуры (класса) C++
Как в выводе nm разделить static и const? C++ Linux
C++ Как сравнить char и const char?
Ошибка: E2034 Cannot convert 'char const[51]' to 'const wchar_t * C++ Builder
Visual C++ Невозможно преобразовать параметр 1 из 'const char [3]' в 'const wchar_t *

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3635 / 1910 / 503
Регистрация: 18.10.2014
Сообщений: 3,476
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char* #15
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Если где-то в коде PTR окажется использованным как lvalue, то придется его еще где-то определить. Инициализатор в определении повторять не надо.
Тут, кстати, стоит добавить, что в С++17 все сильно меняется.

В С++17 вводится понятие inline variable. И можно будет писать так

C++
1
2
3
4
struct S
{
  inline static int x = 42;
}
при этом вышеприведенное объявление переменной S::x является ее определением. Переменную можно использовать как lvalue, и при этом какого-то дополнительного определения для нее делать не надо (и нельзя).

Далее, constexpr константы в С++17 становятся частным случаем inline variable. При этом, для сохранения обратной совместимости, писать отдельное "определение" для такой константы разрешается, но является deprecated

C++
1
2
3
4
5
6
struct S
{
  static constexpr int x = 42; // теперь это - определение
};
 
constexpr int S::x; // пока можно, но deprecated
Yandex
Объявления
22.09.2016, 22:19     Как компилятор обрабатывает член класса static constexpr const char*
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru