非機密扱い | ![]() | ARM DUI0474JJ | ||
| ||||
ホーム > イメージの構造と生成 > 弱参照と定義 |
弱参照と定義を使用することで、リンカはより柔軟な方法でビルドにさまざまな関数や変数を含めることができます。
弱参照と定義は通常、ライブラリ関数への参照です。
リンカでは、通常の非弱参照をこれまでにロードされた内容からシンボルに解決できない場合、ライブラリ内のシンボルを検索して解決を試みます。
そうした参照を見つけることができなかった場合、リンカによってエラーが報告されます。
そうした参照が解決されると、少なくとも 1 つの非弱参照によってエントリポイントから到達できるセクションは、使用済みとマークされます。こうすることで、このセクションが未使用のセクションとしてリンカによって削除されることがなくなります。各非弱参照は、1 つの定義によってのみ解決される必要があります。複数の定義が存在する場合、リンカによってエラーが報告されます。
弱参照を解決するために、リンカによってライブラリからオブジェクトがロードされることはありません。弱参照は、定義が他の理由でイメージに含まれている場合にのみ解決できます。弱参照によって、リンカが定義を含むセクションを使用済みとマークすることはありません。そのため、未使用のセクションとしてリンカによって削除される可能性があります。以下のさまざまな理由で、定義がイメージ内に既に存在している場合があります。
シンボルが、コード内の他の場所からの非弱参照である。
シンボル定義が、これらのいずれかの理由で含まれているシンボル定義と同じ ELF セクション内に存在している。
シンボル定義が、
--keep
を使用して指定されているセクション、または ENTRY
ポイントを含むセクション内に存在している。
シンボル定義がリンクに含まれているオブジェクトファイル内に存在し、
--no_remove
オプションが使用されている。ライブラリ内のオブジェクトファイルが明示的にリンカコマンドラインに含まれている場合を除き、オブジェクトファイルはライブラリから参照されません。
要約すると、弱参照は定義がイメージに既に含まれている場合に解決されますが、その定義が含まれているかどうかの確認は行いません。
未解決の weak 関数の呼び出しは、以下のいずれかに置き換えられます。
非演算命令 NOP
リンク命令 BL
を使用した、後続の命令への分岐。(つまり、関数呼び出しは行われません)
関数定義、つまりアセンブラのエクスポートされたラベルは、変数の定義と同じように、weak としてマークすることもできます。この場合、weak シンボル定義がオブジェクトファイル内に作成されます。
通常の定義と同様の方法で、weak 定義を使用して、そのシンボルへの参照を解決できます。ただし、そのシンボルの non-weak 定義がビルド内に存在する場合、リンカでは weak 定義の代わりに、その定義を使用します。複数回定義されたシンボルが原因で、エラーは生成されません。
ライブラリには、関数 foo()
が含まれています。この関数は、アプリケーションの一部のビルドで呼び出され、その他のビルドでは呼び出されません。この関数を使用する場合は、init_foo()
を最初に呼び出す必要があります。弱参照を使用して、init_foo()
への呼び出しを自動化できます。
ライブラリでは、init_foo()
と foo()
を同じ ELF セクション内に定義できます。アプリケーションの初期化コードでは、init_foo()
を weak で呼び出す必要があります。何らかの理由でアプリケーションに foo()
が含まれている場合は、
init_foo()
も含まれており、それが初期化コードから呼び出されます。foo()
が含まれていないビルドでは、init_foo()
への呼び出しはリンカによって削除されます。
通常、1 つのソースファイル内で定義されている複数の関数のコードは、コンパイラによって、1 つの ELF セクションに配置されます。ただし、特定のビルドオプションによってこの動作が変更される場合があります。そのため、ビルドが ELF セクションへのファイルのグループ化に依存している場合は、注意して使用する必要があります。
コンパイラコマンドラインオプション --split_sections
を指定すると、各関数は独自のセクションに配置されます。この例では、このオプションを指定してライブラリをコンパイルすると、foo()
と init_foo()
は別々のセクションに配置されます。そのため、foo()
への呼び出しが原因で、init_foo()
は自動的にはビルドに含まれません。
リンカフィードバックメカニズム --feedback
を指定すると、
リンク手順で init_foo()
は未使用と記録されます。そのため、その後のコンパイルで init_foo()
は独自のセクションに配置され、削除できるようになります。
コンパイラディレクティブ #pragma arm section
を指定した場合も、コンパイラによって、一部の関数に対して個別の ELF セクションが生成されます。
この例では、foo()
を含むビルドと、foo()
を含まないビルトとで初期化コードをビルドし直す必要はありません。また、init_foo()
を呼び出さないバージョンの初期化コードと、アプリケーションの他の部分で foo()
を呼び出すアプリケーションが誤ってビルドされる可能性もありません。
通常、ライブラリにビルドされる foo.c ソースコードの例を以下に示します。
void init_foo() { // 初期化コード } void foo() { // 一部のビルドに含まれ // init_foo() を最初に呼び出す必要がある関数。}
init.c の例を以下に示します。
__weak void init_foo(void); int main(void) { init_foo(); // foo() を直接または間接的に呼び出したりする残りのコード。}
アセンブラによって生成される弱参照の例を以下に示します。
init.s:IMPORT init_foo WEAK AREA init, CODE, readonly BL init_foo ;Rest of code END
関数の単純な実装またはダミーの実装を weak 定義として使用できます。これらを使用すると、関数の完全な実装を含めることなく動作が定義されたソフトウェアをビルドできます。また、必要に応じて、一部のビルドに完全な実装を含めることもできます。