Ответ 1
Так как bar
довольно большой, компилятор генерирует статическое распределение вместо автоматического распределения в стеке. Статические массивы создаются с помощью директивы сборки .comm
, которая создает распределение в так называемом разделе COMMON. Символы из этого раздела собираются, одинаковые имена объединяются (сводятся к одному запросу символа с размером, равным наибольшему запрошенному размеру), а затем то, что покой, сопоставляется с разделом BSS (неинициализированные данные) в большинстве исполняемых форматов. С помощью исполняемых файлов ELF секция .bss
находится в сегменте данных непосредственно перед частью сегмента данных кучи (имеется еще одна часть кучи, управляемая отображениями анонимной памяти, которые не находятся в сегменте данных).
В модели памяти small
для адресации символов x86_64 используются 32-разрядные инструкции адресации. Это делает код меньше и быстрее. Некоторая сборка при использовании модели памяти small
:
movl $bar.1535, %ebx <---- Instruction length saving
...
movl %eax, baz_+4(%rip) <---- Problem!!
...
.local bar.1535
.comm bar.1535,2575411200,32
...
.comm baz_,12,16
Это использует 32-битную инструкцию перемещения (длиной 5 байт), чтобы поместить значение символа bar.1535
(это значение равно адресу места символа) в нижние 32 бита регистра RBX
( верхние 32 бита обнуляются). Сам символ bar.1535
выделяется с помощью директивы .comm
. После этого выделяется память для блока baz
COMMON. Поскольку bar.1535
очень большой, baz_
заканчивается более чем на 2 гигабайта с начала раздела .bss
. Это создает проблему во второй инструкции movl
, поскольку для обращения к переменной b
, где должно быть перенесено значение EAX
, необходимо использовать не 32-битное (подписанное) смещение от RIP
. Это обнаружено только во время соединения. Сам ассемблер не знает соответствующего смещения, так как он не знает, что будет означать значение указателя инструкции (RIP
) (оно зависит от абсолютного виртуального адреса, где загружается код, и это определяется компоновщиком), поэтому он просто переносит смещение 0
, а затем создает запрос на перемещение типа R_X86_64_PC32
. Он инструктирует компоновщик исправить значение 0
с реальным значением смещения. Но он не может этого сделать, поскольку значение смещения не будет помещаться внутри подписанного 32-битного целого и, следовательно, выйдет из строя.
При использовании модели памяти medium
все выглядит так:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
Сначала выполняется 64-битная команда немедленного перемещения (длиной 10 байт), чтобы поместить 64-битное значение, которое представляет адрес bar.1535
, в регистр R10
. Память для символа bar.1535
выделяется с помощью директивы .largecomm
и, следовательно, заканчивается в секции .lbss
ELF exectuable. .lbss
используется для хранения символов, которые могут не соответствовать в первых двух GiB (и, следовательно, не должны быть адресованы с использованием 32-разрядных инструкций или RIP-относительной адресации), в то время как меньшие вещи идут в .bss
(baz_
все еще выделяется с помощью .comm
, а не .largecomm
). Поскольку раздел .lbss
размещается после раздела .bss
в компоновщике ELF script, baz_
не будет недоступен при использовании 32-разрядной адресации, связанной с RIP.
Все режимы адресации описаны в System V ABI: AMD64 Architecture Processor Supplement. Это тяжелое техническое чтение, но оно должно читать для тех, кто действительно хочет понять, как работает 64-разрядный код для большинства x86_64 Unixes.
Если вместо этого используется массив ALLOCATABLE
, gfortran
выделяет кучную память (скорее всего, реализован как анонимная карта памяти с учетом большого размера выделения):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
Это в основном RDI = malloc(2575411200)
. С этого момента элементы bar
получают путем использования положительных смещений от значения, хранящегося в RDI
:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
Для мест с более чем 2 ГБ с начала bar
используется более сложный метод. Например. для реализации b = bar(12,144*144*450)
gfortran
испускает:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
Этот код не влияет на модель памяти, поскольку ничего не предполагается относительно адреса, в котором будет выполняться динамическое распределение. Кроме того, поскольку массив не передается, дескриптор не создается. Если вы добавите еще одну функцию, которая принимает массив предполагаемой формы и передаст ей bar
, дескриптор для bar
создается как автоматическая переменная (т.е. В стеке foo
). Если массив сделан статическим с атрибутом SAVE
, дескриптор помещается в раздел .bss
:
movl $bar.1580, %edi
...
; RAX still holds the address of the allocated memory as returned by malloc
; Computations, computations
movl -232(%rax,%rdx,4), %eax
movl %eax, baz_+4(%rip)
Первый шаг подготавливает аргумент вызова функции (в моем примере примера call boo(bar)
, где boo
имеет интерфейс, объявляющий его как принимающий массив формы). Он перемещает адрес дескриптора массива bar
в EDI
. Это 32-битный немедленный ход, поэтому ожидается, что дескриптор будет находиться в первых двух гигабайтах. Действительно, он выделяется в .bss
в моделях памяти small
и medium
следующим образом:
.local bar.1580
.comm bar.1580,72,32
.local bar.1580
.comm bar.1580,72,32