リンカスクリプトで変数を宣言するには
・.(ロケーションカウンダ)をインクリメントする方法
・BYTE()、SHORT()、LONG()などのビルトイン関数を使う方法
の主に二つが存在する。
前者は初期値を設定しなくてよい場合に手頃だが、初期値も欲しい場合は後者を採用する必要がある。
BYTE(0x12)、SHORT(0x1234)などとすることで、カッコ内に書いた値が初期値となる。
リンカスクリプトのよくある誤解として
value = 0x12345678;
と書いたら、初期値が0x12345678の変数が作られるとの思い込みがある。
実際にはそうではなく、0x12345678番地にvaluleというシンボルが作られるだけであり、valueが実際にメモリ上に確保されるようなことはない。
この点がC言語との文法と食い違っていてすこし分かりにくいのだが...
なので、実際にメモリを確保する変数を定義する場合は次のようにする必要がある。
ちなみにこれは初期値をもたない4byte変数を作成している。
value = .;
value += 4;
初期値ありの場合は次のようになるだろうか
vaue = .;
LONG(0x12345678);
二つ注意があって、一つ目はリンカスクリプトにおける変数は勝手にアライメントしてくれないということである。
まぁ、勝手にされたらたまったものではないのだが、これを調節するALIGNビルトイン関数を使ってやる必要がある。
もう一つはBYTE、SHORT、LONGといった関数は出力ファイルのセクション定義部分にしかかけないということである。
例えば、次のようになる
// OK
SECTIONS{
.text : { *(.text) }
.data : { value1 = .; LONG(0x12345678); *(.data) }
}
// BAD
SECTIONS{
.text : { *(.text) }
value1 = .; LONG(0x12345678);
.data : { *(.data) }
}
また、次のコードではアライメントがおかしなことになっていて、アライメントに寛容なx86とはいえ、実行速度に影響が出るかもしれない。
// 例えば、value1のアドレスが0x1000だとすると...
value1 = .;
BYTE(1);
// value2のアドレス = 0x1001
value2 = .;
SHORT(2);
// value3のアドレス = 0x1003
value3 = .;
LONG(3);
これはALIGN関数を使うことで次のように修正できる。
// 例えば、value1のアドレスが0x1000だとすると...
value1 = .;
BYTE(1);
ALIGN(2);
// value2のアドレス = 0x1002
value2 = .;
SHORT(2);
ALIGN(4);
// value3のアドレス = 0x1004
value3 = .;
LONG(3);
非常に頻出する関数なのでぜひ覚えてほしい。
また、ALIGNなどで作られた空白のメモリ空間はFILLビルトイン関数を使うことで、その空き空間に入る値を指定できる。
もしくは出力ファイルのセクションの最後に「= 値」といった風な記述をすることでも、同様の効果が望める。
優先度はFILLビルトイン関数のほうが高い。
section [address] [(type)] : [AT(lma)] {
/* FILL(ビットパターン); */ output-section-command output-section-command … } [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp]
この変数は当然、C言語側で利用することができ、次のように使ったりする。
extern char value1;
extern short value2;
extern int value3;
int main(void){
printf("value1 = %d, value2 = %d, value3 = %d\n", value1, value2, value3);
return 0;
}