мучает вопрос, как внутри работает область видимости в различных языках? Ну или хотя бы интересно было бы узнать общую концепцию. как например компилятор си (/c++/c#/java/...) позволяет в разных методах использовать одно и тоже название переменной? меняет "название" переменной на стадии компиляции в зависимости от его родителя? а если два потока одновременно выполняясь читают одну и ту же переменную? ведь потоки могут вызываться в неизвестном для компилятора количестве динамически в реалтайме, так же как и динамически могут создаться в ООП объекты имея внутри одинаковые названия переменных. Чтение исходников JVM и cpython не особо помог
Чтобы узнать общую концепцию, стоит почитать книгу дракона. Здесь попробую осветить в общих чертах работу компилятора Java. Компиляция исходника в байткод проходит в несколько стадий:
Интересующий вас процесс происходит на стадиях 2, 4 и 5. Центральный элемент этого процесса - таблица символов. Именно по ней компилятор ориентируется при определении, что и где видимо.
Интересный момент в том, что области видимости переменных в Java существуют и имеют значение только на этапе компиляции, в байткоде этой информации уже не будет. Например попытка скомпилировать такой код
public static void test() {
int a = 1;
{
int b = 2;
}
System.out.println(a);
System.out.println(b);
}
выдаст ошибку "cannot find symbol". Перенесём вывод переменной b
внутрь блока и скомпилируем код с отладочной информацией javac -g Example.java
. А теперь заглянем в байткод javap -c -v -p Example.class
public static void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: iconst_1
1: istore_0
2: iconst_2
3: istore_1
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
8: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_0
15: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
18: return
LineNumberTable:
line 52: 0
line 55: 2
line 56: 4
line 59: 11
line 60: 18
LocalVariableTable:
Start Length Slot Name Signature
4 7 1 b I
2 17 0 a I
Благодаря ключу -g
в самом конце листинга можно увидеть таблицу локальных переменных. JVM эту информацию не использует, для неё есть только слоты - порядковые номера переменных, а об их именах и видимости она ничего не знает. Если опкод iload_0
по 14-му смещению поменять на опкод iload_1
, виртуальная машина без зазрения совести выведет в консоль значение "невидимой" переменной b
.
О полях в байткоде сохраняется чуть больше информации, чем о переменных. Эту информацию компилятор записывает в пул констант - область class-файла до его загрузки и область MetaSpace после загрузки. Каждому полю в пуле соответствует одна запись типа Fieldref
хранящая разделённые точкой указатели на другие области в пуле. Значение до точки указывает на класс, к которому относится поле, а значение после точки указывает на запись типа NameAndType
. Запись NameAndType
в свою очередь тоже хранит два указателя, но уже разделённые двоеточием. Первый указывает на запись хранящую имя поля, а второй на запись хранящую тип. Как говорится, лучше один раз увидеть, чем 100 раз услышать. Скомпилируем, а потом заглянем внутрь этого простого класса:
public class Example {
private int x = 42;
private int getX() {
return x;
}
public static void main(String[] args) {
Example obj = new Example();
obj.getX();
}
}
В самом начале мы увидим пул констант:
Constant pool:
#1 = Methodref #6.#25 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#26 // Example.x:I
#3 = Class #27 // Example
#4 = Methodref #3.#25 // Example."<init>":()V
#5 = Methodref #3.#28 // Example.getX:()I
#6 = Class #29 // java/lang/Object
#7 = Utf8 x
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 LExample;
#16 = Utf8 getX
#17 = Utf8 ()I
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 obj
#23 = Utf8 SourceFile
#24 = Utf8 Example.java
#25 = NameAndType #9:#10 // "<init>":()V
#26 = NameAndType #7:#8 // x:I
#27 = Utf8 Example
#28 = NameAndType #16:#17 // getX:()I
#29 = Utf8 java/lang/Object
Второй элемент пула констант - это информация о поле x
. Чуть ниже в листинге можно найти, как JVM понимает из какого именно объекта нужно взять поле:
0: new #3 // class Example
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokespecial #5 // Method getX:()I
12: pop
13: return
По смещению 8 располагается опкод aload_1
загружающий в стек ссылку на объект obj
. Следом за ним происходит вызов метода getX
.
0: aload_0
1: getfield #2 // Field x:I
4: ireturn
Первое, что делает метод getX
- загружает переданную при вызове ссылку на объект в свой кадр стека. Следующий опкод getfiled
использует эту ссылку, чтобы извлечь значение поля x
именно из того объекта, на которой она указывает. То есть в псевдокоде получение значения x
объекта obj
выглядит примерно так:
void main() {
Example obj = new Example();
Example.getX(obj);
}
int getX(this) {
return this.x;
}
Как через https://githubcom/rubenlagus/TelegramApi создать бота который будет обрабатывать сообщения юзеров ?
Знаю про конечные автоматы только какие-то основы, наподобие определения, сути и тд Подскажите, где подробно почитать про их реализацию? (желательно...
Для запуска метода start() в javafx нужно в main() написать Applicationlaunch(args)
Недавно при просмотре урока по верстке лендинга заметил вот такую запись: