Где хранятся выражения и константы, если нет в памяти?
От языка программирования C Брайана У. Кернигана
& оператор применим только к объектам в памяти: переменные и массив элементы. Он не может применяться к выражениям, константам или регистру переменные.
Где выражения и константы хранятся, если не в памяти?
Что означает эта цитата?
например:
&(2 + 3)
Почему мы не можем принять его адрес? Где он хранится?
Будет ли ответ одинаковым для С++ также, поскольку C является его родителем?
Этот связанный вопрос объясняет, что такие выражения являются объектами rvalue
, а все объекты rvalue
не имеют адресов.
Мой вопрос в том, где хранятся эти выражения, чтобы их адреса не могли быть восстановлены?
Ответы
Ответ 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, если попытаетесь.