非常に大きいためbar
、コンパイラはスタックでの自動割り当てではなく静的割り当てを生成します。静的配列は、.comm
いわゆるCOMMONセクションに割り当てを作成するアセンブリディレクティブを使用して作成されます。そのセクションのシンボルが収集され、同じ名前のシンボルがマージされ(要求された最大サイズに等しいサイズの1つのシンボル要求に縮小され)、残りはほとんどの実行可能形式でBSS(初期化されていないデータ)セクションにマップされます。ELF実行可能ファイルでは、.bss
セクションはヒープのデータセグメント部分の直前のデータセグメントにあります(データセグメントに存在しない匿名メモリマッピングによって管理される別のヒープ部分があります)。
メモリモデルでは、small
32ビットアドレス指定命令を使用してx86_64上のシンボルをアドレス指定します。これにより、コードが小さくなり、高速になります。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
(この値はシンボル位置のアドレスに等しい)をRBX
レジスタの下位32ビットに入れます(上位32ビットはゼロになります)。bar.1535
シンボル自体は、.comm
ディレクティブを使用して割り当てられます。baz
その後、COMMONブロックのメモリが割り当てられます。bar.1535
非常に大きいためbaz_
、セクションの最初から2GiB以上になり.bss
ます。movl
からの非32ビット(符号付き)オフセットを使用して変数RIP
をアドレス指定する必要があるため、これは2番目の命令で問題を引き起こします。b
EAX
に移動する必要があります。これは、リンク時間中にのみ検出されます。アセンブラ自体は、命令ポインタ()の値がわからないため、適切なオフセットを知りません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実行可能セクションで終了します。.lbss
最初の2GiBに収まらない可能性のあるシンボルを格納するために使用されます(したがって、32ビット命令またはRIP相対アドレス指定を使用してアドレス指定し.bss
ないbaz_
で.comm
ください.largecomm
)。このセクションはELFリンカースクリプト.lbss
のセクションの後に配置されるため、 32ビットのRIP関連のアドレス指定を使用してアクセスできなくなることはありません。.bss
baz_
すべてのアドレッシングモードは、System V ABI:AMD64アーキテクチャプロセッササプリメントで説明されています。これは技術的に重い読み物ですが、64ビットコードがほとんどのx86_64Unixでどのように機能するかを本当に理解したい人には必読です。
ALLOCATABLE
代わりに配列を使用する場合は、gfortran
ヒープメモリを割り当てます(割り当てのサイズが大きい場合は、匿名メモリマップとして実装される可能性があります)。
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
これは基本的にRDI = malloc(2575411200)
です。それ以降、の要素は、にbar
格納されている値からの正のオフセットを使用してアクセスされますRDI
。
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
の開始から2GiBを超える場所では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ビットの即時移動であるため、記述子は最初の2GiBにあると予想されます。実際、次のように、メモリモデルとメモリモデル.bss
の両方に割り当てられます。small
medium
.local bar.1580
.comm bar.1580,72,32