Страницы

Поиск по вопросам

воскресенье, 1 декабря 2019 г.

Циклы и прочие штуки, взятые в { … }

#c #cpp


Здравствуйте! Волнует такой вопрос. Есть в с++ такие вот скобки {}. Если в методе,
в них объявить переменную как тут:
void funct(){
int g=0;
  {
     int g=0;
  }
}

то компилятор не будет ругаться. Почему? Если объяснить на языке ассемблера то что
произойдет? Произойдет вызов функции с передачей всех переменных доступных верхней
функции в {}? если да, то как передадутся переменные, в стеке? Для чего вообще может
быть допустима приведенная выше конструкция? И вот еще. Вот этот символ ";" означает
конец операции, например int t=a+b;. А что он выполняет? почему его можно ставить так:
void f(){
;
} или 
 void f(){ };

и ошибки не будет? Что делает компьютер, когда натыкается на ;?
Спасибо.    


Ответы

Ответ 1



{....} - это блок кода и он может быть вставлен везде, где можно вставить одиночную строку. Вас ведь не смущает код: if (....) doSmth(); и if (....) { doSmth(); } Но у этих скобок есть одна особенность - они объявляют новую область видимости. void funct(){ int g=0; { int g=0; // это другая переменная g, она имеет другой адрес! // предыдущая g не доступна по своему имени до закрывающей фигурной скобки. } // а здесь снова доступна первая переменная. } На уровне ассемблера это выглядит так funct(): push rbp mov rbp, rsp mov DWORD PTR [rbp-8], 0 ; первая g mov DWORD PTR [rbp-4], 0 ; вторая g pop rbp ret как передадутся переменные, в стеке? как обычно. Хотя никто не мешает компилятору проанализировать и сделать хорошо. Для чего вообще может быть допустима приведенная выше конструкция? например для любителей длинных функций, когда хочется добавить ещё сотню строк, а переменные (имена) закончились. Но если серьезно, то подобные конструкции используются постоянно - циклы, условия. Вот этот символ ";" означает конец операции, например int t=a+b;. А что он выполняет? он выполняет декоративную роль для программиста. Это синтаксический сахар. Но компилятор может пользоваться им в том случае, если в коде много ошибок, что бы хотя бы как то разделять операторы. Вот в Go точку с запятой можно пропускать, когда и так понятно, что она там должна быть. В ассемблерном коде она никак не отображается.

Ответ 2



@KoVadim уже ответил про области видимости, хочу немного дополнить о распределении памяти (заодно ответ на комментарий @gecube). При входе в блок и выходе из него стек, естественно, не меняется. Компилятор определяет максимальный размер, который займут локальные переменные с учетом переменных локальных для блока и сдвигает регистр стека при входе в функцию на нужную величину. Например в avp@avp-xub11:~/hashcode$ cat foo.c #include #include void foo () { int x = 0, y; printf("first local x = %d at %p\n", x, &x); b1:; // кстати, это (вопрос №2) называется пустой оператор { int x = 1, y1; printf("b1: block x = %d at %p\n", x, &x); } printf("second local x = %d at %p\n", x, &x); b2:; { int x = 2, y2; printf("b2: block x = %d at %p\n", x, &x); } printf("last local x = %d at %p\n", x, &x); } int main (int ac, char *av[]) { foo(); return puts("Bye!") == EOF; } avp@avp-xub11:~/hashcode$ gcc foo.c avp@avp-xub11:~/hashcode$ ./a.out first local x = 0 at 0xbfcaf2f8 b1: block x = 1 at 0xbfcaf2fc second local x = 0 at 0xbfcaf2f8 b2: block x = 2 at 0xbfcaf2fc last local x = 0 at 0xbfcaf2f8 Bye! avp@avp-xub11:~/hashcode$ видно, что переменые x в разных блоках размещаются по одному и тому же адресу. Понятно, что когда компилятор встречает вот такое int arr[x]; объявление локального массива (x это переменная, значение которой во время компиляции неизвестно), то он создает код, сдвигающий регистр sp (указатель стека) в данном месте, а при выходе из области видимости восстанавливает старое значение sp.

Комментариев нет:

Отправить комментарий