Глядя на структуру спецификации JVM, он в основном говорит, что стек содержит фреймы, и что фреймы содержат все, что находится внутри класса, путем правильного распределения переменных и функций. Может быть, я что-то здесь упускаю, но я не понимаю, чем это отличается от того, что делает C ++. Я спрашиваю, потому что первая ссылка говорит, что спецификация содержимого стека Java избегает несовместимости компилятора.
На практике компиляторы C ++ следуют той же базовой стратегии. Однако комитет по стандартам не считает это проблемой языка. Вместо этого компиляторы C ++ следуют этой системе, потому что так устроены большинство процессоров и операционных систем. Различные платформы расходятся во мнениях относительно того, передаются ли данные функциям в стеке или через регистры (машины RISC), растет ли стек или уменьшается, существуют ли разные соглашения о вызовах, позволяющие «обычным» вызовам использовать стек, а другим - использовать somethign иначе (например, __ fastcall и naked ), существует ли такая вещь, как вложенные функции , поддержка хвостовых вызовов и т. д.
Фактически, соответствующий компилятор C ++ может скомпилировать что-то вроде виртуальной машины Scheme, где «стек» сильно отличается, поскольку Scheme требует реализаций для поддержки как хвостовых вызовов, так и продолжений. Я никогда не видел ничего подобного, но это было бы законно.
«Несовместимость компилятора» наиболее очевидна, если вы попытаетесь написать сборщик мусора :
все локальные переменные, как для текущей функции, так и для всех ее вызывающих, находятся в ["" стеке, но рассмотрим ucontext.h и Windows Fibers ]. Для каждой платформы (то есть OS + CPU + компилятор) есть способ узнать, где находится ["стек"]. Тамарин делает это, затем сканирует всю эту память во время GC, чтобы увидеть, на что указывают местные жители. ...
Эта магия живет в макросе MMGC_GET_STACK_EXTENTS, определенном в заголовке MMgc / GC.h. ... [T] вот отдельная реализация для каждой платформы.
В любой момент времени некоторые локальные объекты могут находиться в регистрах ЦП, а не в стеке. Чтобы справиться с этим, макрос использует несколько строк кода сборки, чтобы выгрузить содержимое всех регистров в стек. Таким образом, MMgc может просто сканировать стек и видеть все локальные переменные.
Кроме того, объекты в Java обычно не выделяются в стеке. Вместо этого ссылки на них есть. Типы int, double, boolean и другие примитивы распределяются в стеке. В C ++ все может быть размещено в стеке, который имеет свой список плюсов и минусов.
Еще одна вещь, которую я не понимаю, это пул констант времени выполнения. Предполагается, что это «представление времени выполнения для класса или таблицы для таблицы constant_pool в файле класса», но я не думаю, что понимаю, что она делает.
Рассмотрим:
String s = "Hello World";
int i = "Hello World".length();
int j = 5;
s, i и j - все переменные, и каждая из них может быть изменена на более позднем этапе программы. Однако «Hello World» - это объект типа String, который нельзя изменить, 5 - это int, который нельзя изменить, и «Hello World» .length () можно определить во время компиляции, чтобы всегда возвращать 11. Эти константы допустимые объекты и методы могут быть вызваны для них (ну, по крайней мере, для String), поэтому их нужно где-то размещать. Но они не могут быть изменены, никогда. Если эти константы принадлежат классу, они размещаются в пуле констант для каждого класса. Другие константные данные, которые не являются частью класса (например, идентификатор потока main ()), размещаются в пуле констант для каждой среды выполнения (в данном случае «среда выполнения» означает «экземпляр JVM»).
Стандарт C ++ имеет некоторый язык о подобной технике, но реализация оставлена на усмотрение двоичного формата (ELF, a.out, COFF, PE и т. Д.). Стандарт ожидает, что константы, являющиеся целочисленными типами данных (bool, int, long и т. Д.) Или строки в стиле c, будут фактически храниться в постоянной части двоичного файла, тогда как другие константные данные (double, float, классы) могут храниться как переменная вместе с флагом, говорящим о том, что «переменная» не является изменяемой (также допустимо хранить их с целочисленными и строковыми константами в стиле c, но многие двоичные форматы не делают это опцией).
Вообще говоря, «секция постоянных данных» двоичного файла может использоваться совместно, если одновременно открыто более одной копии программы (поскольку постоянные данные будут идентичны в каждой копии программы). На ELF этот раздел называется разделом .rodata .