Является ли адрес локальной переменной constexpr? - PullRequest
23 голосов
/ 16 апреля 2019

В книге Бьярна Страуструпа "Язык программирования C ++ (4-е издание)" на с. 267 (Раздел 10.4.5 Выражения констант адреса), он использует пример кода, где адрес локальной переменной установлен в переменную constexpr. Я подумал, что это выглядит странно, поэтому я попытался запустить пример с g ++ версии 7.3.0 и не смог получить те же результаты. Вот его пример кода (дословно сокращенно):

extern char glob;

void f(char loc) {
    constexpr const char* p0 = &glob; // OK: &glob's is a constant
    constexpr const char* p2 = &loc;  // OK: &loc is constant in its scope
}

Когда я запускаю это, я получаю:

error: ‘(const char*)(& loc)’ is not a constant expression

Что-то происходит с g ++, о котором я не знаю, или есть что-то еще в примере Бьярне?

Ответы [ 4 ]

17 голосов
/ 16 апреля 2019

Более ранняя печать книги Бьярна Страуструпа "Язык программирования C ++ (4-е издание)" на с. 267 имеет ошибку, изложенную в вопросе ОП. Текущие печатные и электронные копии были «исправлены», но в них была внесена другая ошибка, описанная позже Теперь это относится к следующему коду:

constexpr const char* p1="asdf";

Это нормально, потому что «asdf» хранится в фиксированной ячейке памяти. В более ранней печати книга ошибается здесь:

void f(char loc) {
    constexpr const char* p0 = &glob; // OK: &glob's is a constant
    constexpr const char* p2 = &loc;  // OK: &loc is constant in its scope
}

Однако loc не находится в фиксированной ячейке памяти. он находится в стеке и будет иметь различные местоположения в зависимости от того, когда он вызывается.

Однако в текущей печати 4-го издания есть еще одна ошибка. Это дословный код от 10.5.4:

int main() {
    constexpr const char* p1 = "asdf";
    constexpr const char* p2 = p1;      // OK
    constexpr const char* p3 = p1+2;    // error:  the compiler does not know the value of p1
}

Это неправильно. Компилятор / компоновщик знает значение p1 и может определить значение p1+2 во время соединения. Компилируется просто отлично.

10 голосов
/ 16 апреля 2019

Похоже, что пример из раздела 10.4.5, представленный в моей печатной копии "Языка программирования C ++ (4-е издание)", неверен. И поэтому я пришел к выводу, что адрес локальной переменной не является constexpr.

Пример, по-видимому, обновлен в некоторых версиях PDF, как показано здесь:

enter image description here

3 голосов
/ 17 апреля 2019

Этот ответ пытается прояснить, почему адрес локальной переменной не может быть constexpr, анализируя пример для архитектуры x86-64.

Рассмотрим следующую игрушечную функцию print_addr(), которая отображаетадрес его локальной переменной local_var и рекурсивный вызов n раз:

void print_addr(int n) {
   int local_var{};
   std::cout << n << " " << &local_var << '\n';

   if (!n)
      return; // base case

   print_addr(n-1);  // recursive case
}

При вызове print_addr(2) в моей системе x86-64 был получен следующий вывод:

2 0x7ffd89e2cd8c
1 0x7ffd89e2cd5c
0 0x7ffd89e2cd2c

Как видите, соответствующие адреса local_var различны для каждого вызова на print_addr().Также видно, что чем глубже вызов функции, тем ниже адрес локальной переменной local_var.Это связано с тем, что на платформе x86-64 стек увеличивается вниз (т. Е. С более высоких адресов на более низкие).

Для вышеприведенного вывода стек вызовов будет выглядеть следующим образом наПлатформа x86-64:

                |     . . .     |
Highest address ----------------- <-- call to print_addr(2) 
                | print_addr(2) |    
                -----------------
                | print_addr(1) |
                -----------------
                | print_addr(0) | <-- base case, end of recursion
Lowest address  ----------------- Top of the stack

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

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

1 голос
/ 16 апреля 2019

Просто чтобы добавить к другим ответам, которые указали на ошибку, стандарт C ++ допускает только указатели constexpr только на объекты длительность статического хранения , один за другим, или nullptr. См. [expr.const / 8] конкретно # 8,2 ;

Стоит отметить, что:

  • строковые литералы имеют длительность статического хранения :
  • Исходя из ограничений в объявлении extern переменных, они по своей природе будут иметь длительность статического хранения или длительность локального хранилища потока .

Следовательно, это действительно:

#include <string>

extern char glob;
std::string boom = "Haha";

void f(char loc) {
    constexpr const char* p1 = &glob;
    constexpr std::string* p2 = nullptr;
    constexpr std::string* p3 = &boom;
}
...