Как работает область видимости с++

Как мне известно, компилятор языка с++ переводит код, понятный человеку в машинный. Допустим у нас имеется вот такой код:

#include <iostream>
int main(){

    {
       int a = 1;
       std::cout << a << std::endl;
    }
    
    {
       int a = 2;
       std::cout << a << std::endl;
    }
    
    {
       int a = 3;
       std::cout << a << std::endl;
    }
}

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


Ответы (3 шт):

Автор решения: HolyBlackCat

каждая переменная будет иметь разный адрес

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

Какие имена им будут присвоены после компиляции

После компиляции имена переменных не хранятся.

Если знаете ассемблер, посмотрите результат дизассемблирования с оптимизацией и без нее.

Я там увидел вот что:

  • Со включенной оптимизацией a, b, c оказались в регистре, причем в одном и том же.

  • Со выключенной оптимизацией a, b, c оказались на стеке, в разных местах. Соответственно доступ к ним из ассемблера выглядит как разыменовывание указателя на stack frame с каким-то смещением. Здесь у разных переменных смещение оказалось разное, но раз они не живут одновременно, то оно могло бы быть и одинаковое.

→ Ссылка
Автор решения: Harry

Может быть и еще хуже - вообще никаких переменных, даже в регистре :) - вот что делает VC++ 2019:

; 5    :     {
; 6    :        int a = 1;
; 7    :        std::cout << a << std::endl;

    push    1
    mov ecx, OFFSET ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::cout
    call    ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z 
    push    eax
→ Ссылка
Автор решения: VladD

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

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

Как компилятор отличит их — ну точно так же, как он отличает разные объекты.

Код

{
   int a = 1;
   std::cout << a;
}
{
   int a = 2;
   std::cout << a;
}

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

operator sequence
  ...
   * assignment
       * target ────────────> [int variable]
       * value ──> const 1      ^
   * function call ─────────────┼──┐
       * arg 1 ─────────────────┼──┼────> [global object of type ostream] <──┐
       * arg 2 ─────────────────┘  │                                         │
   * assignment                    └────────> [function << of ostream type]  │
       * target ────────────> [another int variable]    ^                    │
       * value ──> const 2      ^                       │                    │
   * function call ─────────────┼───────────────────────┘                    │
       * arg 1 ─────────────────┼────────────────────────────────────────────┘
       * arg 2 ─────────────────┘
  ...

Как видите, имена оказались не нужны.

→ Ссылка