Подтвердить что ты не робот

Где хранятся выражения и константы, если нет в памяти?

От языка программирования C Брайана У. Кернигана

& оператор применим только к объектам в памяти: переменные и массив элементы. Он не может применяться к выражениям, константам или регистру переменные.

Где выражения и константы хранятся, если не в памяти? Что означает эта цитата?

например:
&(2 + 3)

Почему мы не можем принять его адрес? Где он хранится?
Будет ли ответ одинаковым для С++ также, поскольку C является его родителем?

Этот связанный вопрос объясняет, что такие выражения являются объектами rvalue, а все объекты rvalue не имеют адресов.

Мой вопрос в том, где хранятся эти выражения, чтобы их адреса не могли быть восстановлены?

4b9b3361

Ответ 1

Рассмотрим следующую функцию:

unsigned sum_evens (unsigned number) {
  number &= ~1; // ~1 = 0xfffffffe (32-bit CPU)
  unsigned result = 0;
  while (number) {
    result += number;
    number -= 2;
  }
  return result;
}

Теперь, давайте играть в игру компилятора и попытаться скомпилировать это вручную. Я предполагаю, что вы используете x86, потому что это то, что используют большинство настольных компьютеров. (x86 - это набор команд для совместимых с Intel процессоров.)

Пропустите простую (неоптимизированную) версию того, как эта подпрограмма может выглядеть при компиляции:

sum_evens:
  and edi, 0xfffffffe ;edi is where the first argument goes
  xor eax, eax ;set register eax to 0
  cmp edi, 0 ;compare number to 0
  jz .done ;if edi = 0, jump to .done
.loop
  add eax, edi ;eax = eax + edi
  sub edi, 2 ;edi = edi - 2
  jnz .loop ;if edi != 0, go back to .loop
.done
  ret ;return (value in eax is returned to caller)

Теперь, как вы можете видеть, константы в коде (0, 2, 1) фактически отображаются как часть инструкций CPU! Фактически, 1 не появляется вообще; компилятор (в данном случае, только я) уже вычисляет ~1 и использует результат в коде.

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

Заметьте, что while (number) эквивалентно while (number != 0). Это 0 вообще не отображается в скомпилированном коде! Это подразумевается командой jnz (скачок, если не ноль). Это еще одна причина, по которой вы не можете принять адрес этого 0 - у него его нет, это буквально нигде.

Надеюсь, это станет более понятным для вас.

Ответ 2

где хранятся эти выражения так, что адреса не могут быть получены?

Ваш вопрос неверен.

  • Концептуально

    Ему нравится спрашивать, почему люди могут обсуждать владение существительными, но не глаголами. Существительные ссылаются на вещи, которые могут (потенциально) принадлежать, а глаголы относятся к действиям, которые выполняются. Вы не можете владеть действием или выполнять что-то.

  • С точки зрения спецификации языка

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

  • С точки зрения реализации языка

    Рассмотрим утверждение

    int a = 0;
    

    Это делает две вещи: во-первых, объявляет целочисленную переменную a. Это определяется как то, чей адрес вы можете взять. Это до компилятора, чтобы делать все, что имеет смысл на данной платформе, чтобы вы могли принять адрес a.

    Во-вторых, оно устанавливает значение переменной в ноль. Это не означает, что целое число со значением 0 существует где-то в вашей скомпилированной программе. Обычно это может быть реализовано как

    xor eax,eax
    

    то есть регистр XOR (эксклюзивный или) eax регистрируется сам по себе. Это всегда приводит к нулю, независимо от того, что было раньше. Однако в компилируемом коде нет фиксированного объекта значения 0 для соответствия целочисленному литералу 0, который вы написали в источнике.

В стороне, когда я говорю, что a выше - это то, чей адрес вы можете принять - стоит отметить, что у него может не быть адреса, если вы его не возьмете. Например, регистр eax, используемый в этом примере, не имеет адреса. Если компилятор может доказать, что программа по-прежнему правильная, a может прожить всю свою жизнь в этом регистре и никогда не существовать в основной памяти. И наоборот, если вы используете выражение &a где-то, компилятор позаботится о создании некоторого адресуемого пространства для хранения значения a.


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

Вероятно, это будет интерпретироваться, поскольку компиляция обычно отбрасывает эти структуры после того, как машинный исполняемый файл заменяет их. Например, Python имеет интроспекцию времени выполнения и объекты code.

Или я могу начать с LISP и расширить его, чтобы обеспечить какой-то адрес операции в S-выражениях.

Ключевая вещь, которую они оба имеют вместе, заключается в том, что они не являются C, которые, как вопрос проектирования и определения, не обеспечивают эти механизмы.

Ответ 3

Такие выражения становятся частью машинного кода. Выражение 2 + 3, вероятно, переводится в инструкцию машинного кода "загрузить 5 в регистр A". Регистры процессора не имеют адресов.

Ответ 4

На самом деле нет смысла брать адрес в выражение. Самое близкое, что вы можете сделать, это указатель на функцию. Выражения не сохраняются в том же смысле, что и переменные и объекты.

Выражения сохраняются в фактическом машинных кодах. Конечно, вы можете найти адрес, где выражение оценивается, но просто не имеет смысла это делать.

Прочитайте немного о сборке. Выражения сохраняются в текстовом сегменте, а переменные хранятся в других сегментах, таких как данные или стек.

https://en.wikipedia.org/wiki/Data_segment

Другим способом объяснить это то, что выражения являются командами cpu, а переменные - чистыми данными.

Еще одна вещь, которую следует учитывать: компилятор часто оптимизирует вещи. Рассмотрим этот код:

int x=0;
while(x<10)
    x+=1;

Этот код будет, вероятно, оптимизирован для:

int x=10;

Итак, что бы означал в этом случае адрес (x+=1)? Он даже не присутствует в машинный код, поэтому он имеет - по определению - никакого адреса вообще.

Ответ 5

Где выражения и константы хранятся, если не в памяти

В некоторых (фактически многих) случаях константное выражение не сохраняется вообще. В частности, подумайте о оптимизации компиляторов и см. CppCon 2017: Matt Godbolt talk "Что мой компилятор для меня сделал последнее время? Отпирание крышки компилятора"

В вашем конкретном случае с некоторым кодом C, имеющим 2 + 3, большинство оптимизирующих компиляторов будет сложенная константа, что в 5, и что 5 константа может быть только внутри некоторой команды машинный код (как некоторое битовое поле) вашего сегмент кода и даже не имеет четко определенной ячейки памяти. Если эта константа 5 была пределом цикла, некоторые компиляторы могли бы сделать разворот цикла, и эта константа больше не будет отображаться в двоичном коде.

См. также этот ответ и т.д.

Помните, что C11 - это спецификация, написанная на английском языке. Прочтите стандарт n1570. Читайте также гораздо большую спецификацию С++ 11 (или более поздней).

Взятие адреса константы запрещено semantics в C (и С++).

Ответ 6

Ответы слишком сложны. То, что на самом деле означает ваш первоначальный оператор, относится к значениям в памяти стека или в куче памяти, то есть в памяти, на которую вы действительно можете писать. Выражения и константы, которые являются самой программой, фактически хранятся в памяти, но вы не можете писать в эту память. Поэтому нет смысла иметь возможность ссылаться на эту память; вы получите segfault, если попытаетесь.