Как имена переменных хранятся в памяти в C?
В C, скажем, у вас есть переменная с именем variable_name
, Допустим, он расположен на 0xaaaaaaaa
и по этому адресу памяти у вас есть целое число 123. Таким образом, другими словами, variable_name
содержит 123
Я ищу разъяснения по поводу формулировкиvariable_name
находится по адресу 0xaaaaaaaa
". Как компилятор распознает, что строка" имя_переменной "связана с этим конкретным адресом памяти? Строка" имя_переменной "хранится где-то в памяти? Компилятор просто подставляет variable_name
за 0xaaaaaaaa
всякий раз, когда он видит это, и если так, разве ему не придется использовать память для того, чтобы сделать эту замену?
5 ответов
Имена переменных больше не существуют после запуска компилятора (за исключением особых случаев, таких как экспортированные глобальные переменные в общих библиотеках или символы отладки). Весь процесс компиляции предназначен для того, чтобы взять эти символические имена и алгоритмы, представленные в вашем исходном коде, и превратить их в собственные машинные инструкции. Так что да, если у вас есть глобальный variable_name
и компилятор и компоновщик решают поместить его в 0xaaaaaaaa
тогда, где бы он ни использовался в коде, он будет доступен через этот адрес.
Итак, чтобы ответить на ваши буквальные вопросы:
Как компилятор распознает, что строка "имя-переменной" связана с этим конкретным адресом памяти?
Цепочка инструментов (компилятор и компоновщик) работает вместе, чтобы назначить место в памяти для переменной. Работа компилятора состоит в том, чтобы отслеживать все ссылки, а компоновщик вводит правильные адреса позже.
Строка
"variable_name"
хранится где-то в памяти?
Только во время работы компилятора.
Компилятор просто подставляет
variable_name
за0xaaaaaaaa
всякий раз, когда он видит это, и если так, разве ему не придется использовать память для того, чтобы сделать эту замену?
Да, так и происходит, за исключением двухэтапной работы с компоновщиком. И да, он использует память, но это память компилятора, а не что-либо во время выполнения вашей программы.
Пример может помочь вам понять. Давайте попробуем эту программу:
int x = 12;
int main(void)
{
return x;
}
Довольно просто, правда? ХОРОШО. Давайте возьмем эту программу, скомпилируем ее и посмотрим на разборку:
$ cc -Wall -Werror -Wextra -O3 example.c -o example
$ otool -tV example
example:
(__TEXT,__text) section
_main:
0000000100000f60 pushq %rbp
0000000100000f61 movq %rsp,%rbp
0000000100000f64 movl 0x00000096(%rip),%eax
0000000100000f6a popq %rbp
0000000100000f6b ret
Видеть, что movl
линия? Он захватывает глобальную переменную (в данном случае относительным указателем инструкции). Нет больше упоминания о x
,
Теперь давайте немного усложним и добавим локальную переменную:
int x = 12;
int main(void)
{
volatile int y = 4;
return x + y;
}
Разборка для этой программы:
(__TEXT,__text) section
_main:
0000000100000f60 pushq %rbp
0000000100000f61 movq %rsp,%rbp
0000000100000f64 movl $0x00000004,0xfc(%rbp)
0000000100000f6b movl 0x0000008f(%rip),%eax
0000000100000f71 addl 0xfc(%rbp),%eax
0000000100000f74 popq %rbp
0000000100000f75 ret
Сейчас есть два movl
инструкции и addl
инструкция. Вы можете видеть, что первый movl
инициализирует y
, который решено будет в стеке (базовый указатель - 4). Тогда следующий movl
получает глобальный x
в реестр eax
и addl
добавляет y
к этому значению. Но, как вы можете видеть, буквальное x
а также y
Строки больше не существуют. Они были удобны для вас, программист, но компьютер, конечно, не заботится о них во время выполнения.
Компилятор A C сначала создает таблицу символов, в которой хранится связь между именем переменной и местом ее расположения в памяти. При компиляции он использует эту таблицу для замены всех экземпляров переменной в определенном месте в памяти, как заявили другие. Вы можете найти намного больше об этом на странице Википедии.
Все переменные подставляются компилятором. Сначала они заменяются ссылками, а затем компоновщик размещает адреса вместо ссылок.
Другими словами. Имена переменных больше не доступны, как только компилятор пробежал
Это то, что называется деталью реализации. Хотя то, что вы описываете, относится ко всем компиляторам, которые я когда-либо использовал, это не обязательно так. Компилятор A C может поместить каждую переменную в хеш-таблицу и найти их во время выполнения (или что-то в этом роде), и фактически ранние интерпретаторы JavaScript сделали именно это (теперь они выполняют компиляцию Just-In-TIme, что приводит к чему-то гораздо более сырому).
В частности, для распространенных компиляторов, таких как VC++, GCC и LLVM: компилятор обычно назначает переменную для местоположения в памяти. Переменные глобальной или статической области видимости получают фиксированный адрес, который не изменяется во время работы программы, а переменные внутри функции получают адрес стека, то есть адрес относительно текущего указателя стека, который изменяется каждый раз, когда функция называется. (Это чрезмерное упрощение.) Адреса стека становятся недействительными, как только функция возвращается, но имеют преимущество, заключающееся в том, что использование практически не требует дополнительных затрат.
Как только переменной назначен адрес, больше нет необходимости в имени переменной, поэтому она отбрасывается. В зависимости от типа имени, имя может быть отброшено во время предварительной обработки (для имен макросов), времени компиляции (для статических и локальных переменных / функций) и времени соединения (для глобальных переменных / функций.) Если символ экспортируется (становится видимым для других программ, чтобы они могли к нему обращаться), имя обычно остается где-то в "таблице символов", которая занимает тривиальный объем памяти и дискового пространства.
Компилятор просто заменяет переменное_имя на 0xaaaaaaaa всякий раз, когда он его видит
Да.
и если так, разве ему не пришлось бы использовать память для того, чтобы сделать эту замену?
Да. Но это компилятор, после того как он скомпилировал ваш код, почему вы заботитесь о памяти?