非常に大きいためbar、コンパイラはスタックでの自動割り当てではなく静的割り当てを生成します。静的配列は、.commいわゆるCOMMONセクションに割り当てを作成するアセンブリディレクティブを使用して作成されます。そのセクションのシンボルが収集され、同じ名前のシンボルがマージされ(要求された最大サイズに等しいサイズの1つのシンボル要求に縮小され)、残りはほとんどの実行可能形式でBSS(初期化されていないデータ)セクションにマップされます。ELF実行可能ファイルでは、.bssセクションはヒープのデータセグメント部分の直前のデータセグメントにあります(データセグメントに存在しない匿名メモリマッピングによって管理される別のヒープ部分があります)。
メモリモデルでは、small32ビットアドレス指定命令を使用して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番目の命令で問題を引き起こします。bEAXに移動する必要があります。これは、リンク時間中にのみ検出されます。アセンブラ自体は、命令ポインタ()の値がわからないため、適切なオフセットを知りません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関連のアドレス指定を使用してアクセスできなくなることはありません。.bssbaz_
すべてのアドレッシングモードは、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の両方に割り当てられます。smallmedium
.local bar.1580
.comm bar.1580,72,32