Почему const int не оптимизируется компилятором (через таблицу символов), если другой указатель указывает на его ссылку? - PullRequest
0 голосов
/ 17 ноября 2018

Это ответ на вопрос в этом вопросе: Какую оптимизацию предлагает const в C / C ++?(если есть)

В ответе с наибольшим количеством голосов указано следующее:

Когда вы объявляете const в вашей программе,

int const x = 2;

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

ПРИМЕЧАНИЕ: - Если вы сделаете что-то вроде ниже: -

const int x = 1;
const int* y = &x;

Затем это заставит компилятор выделять место для 'x' .Таким образом, эта степень оптимизации невозможна в этом случае.

Почему это так?Похоже, вы никогда не сможете изменить x, нет?

1 Ответ

0 голосов
/ 17 ноября 2018

ИМХО, Питер предоставил объяснение в своем комментарии:

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

, и это именно то, что я тоже думаю.

The const в C ++ немного сбивает с толку.Это выглядит как сокращение от «константа», но на самом деле это означает «только для чтения».

Имея это в виду, я никогда не задумывался, почему следующий код допустим в C:

enum { N = 3 };
static int a[N]; /* legal in C: N is a constant. */

но это не так:

const int n = 3;
static int b[n]; /* illegal in C: n is a read-only variable */

Когда я переключился на C ++, я принимал вышеизложенное для C ++, пока не понял в беседе с коллегой, что я был неправ.(Не то чтобы это нарушало любой мой письменный код, но я ненавижу, что это неправильно.); -)

const int n = 3;
static int b[n]; // legal in C++

и Const-распространение - это метод, который делает его законным.

Однако даже при распространении const const int x; все еще остается переменной только для чтения, к которой можно обратиться.

ОП предоставил ссылку на эту тему (которая могла бы объяснить это даже лучше, чем выше):

SO: почему размер массива как константы не разрешен в C, но разрешен в C ++?

Чтобы сделать этот полнофункциональный ответ, я попыталсяподготовить образец, который иллюстрирует различия:

#include <iostream>

const int x1 = 1;
static const int x1s = 11;
extern const int x1e = 12;

const int x2 = 2;
extern const int *const pX2 = &x2;

const int x3 = 3;
static const int *const pX3 = &x3;

int main()
{
  // make usage of values (to have a side-effect)
  std::cout << x1;
  std::cout << x1s;
  std::cout << x1e;
  std::cout << x2;
  std::cout << pX2;
  std::cout << x3;
  std::cout << pX3;
  // done
  return 0;
}

и результат gcc 8.2 с -O3:

; int main()
main:
; {
 sub rsp, 8
;   // make usage of values (to have a side-effect)
;   std::cout << x1;
  mov esi, 1
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSolsEi
;   std::cout << x1s;
  mov esi, 11
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSolsEi
;   std::cout << x1e;
  mov esi, 12
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSolsEi
;   std::cout << x2;
  mov esi, 2
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSolsEi
;   std::cout << pX2;
  mov esi, OFFSET FLAT:_ZL2x2
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSo9_M_insertIPKvEERSoT_
;   std::cout << x3;
  mov esi, 3
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSolsEi
;   std::cout << pX3;
  mov esi, OFFSET FLAT:_ZL2x3
  mov edi, OFFSET FLAT:_ZSt4cout
  call _ZNSo9_M_insertIPKvEERSoT_
;   // done
;   return 0;
; }
  xor eax, eax
  add rsp, 8
  ret

и ИМХО самая интересная часть - глобальные переменные:

; const int x3 = 3;
_ZL2x3:
  .long 3
; extern const int *const pX2 = &x2;
pX2:
  .quad _ZL2x2
; const int x2 = 2;
_ZL2x2:
  .long 2
; extern const int x1e = 12;
x1e:
  .long 12
  1. x1, x1s и pX3 были оптимизированы, поскольку они const и не помечены для внешней связи.

  2. x1e и pX2 были выделены, поскольку они помечены для внешней связи.

  3. x2 выделено, поскольку оно обозначено pX2, который отмечен для внешней связи.(Кое-что из extern может получить доступ к x2 через pX2.)

  4. x3 было самым сложным для меня.pX3 было использовано (в std::cout << pX3;).Хотя само его значение указывается в значении x3.Кроме того, хотя доступ к x3std::cout << x3;) также был встроен, использование указателя pX3, инициализированного с &x3, не позволило оптимизировать это хранилище.

Демонстрация в реальном времени на Godbolt (с приятным цветным двойным обзором, облегчающим исследование)

Я сделал то же самоес clang 7.0.0 и результат был аналогичным.

(я пробовал также с msvc v19.15, но я не смог (недостаточно терпеливо) оценить результат.)


Что касается 4., я попытался дополнительно:

const int x4 = 4;
static const int *const pX4 = &x4;

и добавил к main():

  std::cout << x4;
  // No: std::cout << pX4;

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

Это удивительно ...

...