はじめにこのテクニカルノートでは、Xcode2.2でサポートされていない、C言語のGCC拡張機能であるネストされた関数を使ったコードを移植する方法について説明します。Xcodeの今後のリリースでは下位互換性確保のために、コンパイラスイッチ(-fnested-functions)と、対応するビルド設定を導入する予定です。ただし、ネストされた関数は引き続きデフォルトでは無効です。Mac OS Xのセキュリティ機能をアプリケーション側で利用できなくなってしまうので、下位互換性確保モードの使用は避けるべきです。 このテクニカルノートは、Xcode 2.2のインストール後、「nested functions are not supported on MacOSX.(ネストされた関数はMacOSXではサポートされていません)」のメッセージが表示されてアプリケーションのビルドに失敗した、アプリケーション開発者を対象としています。このエラーが表示されない場合、ネストされた関数はコードに使われていないため、このテクニカルノートを読む必要はありません(アプリケーションでGNU Autoconfを使用していない限り。後述の「ネストされた関数とGNU Autoconf」を参照してください)。 先頭に戻る  ネストされた関数についてネストされた関数とはC言語のGCC拡張機能で、関数の宣言を、別の関数の中で行えるようにするものです。例を示します。
void x(int a) {
int b;
...
void y() {
...
}
...
}
この例では、関数y()は関数xの内部でのみ呼び出せます。aやbのような、xのスコープ内の変数はすべてアクセスできます。 ネストされた関数はそれほど広くは使われていません。Fink Projectの2500個のオープンソースアプリケーションを取り出して調査しても、この機能を使用しているのは6つのみでした。 ネストされた関数をサポートするために、コンパイラは、「スタックトランポリン」と呼ばれる方法を使ってコード生成する必要がありますが、この形式のコードはシナリオによってはシステムのセキュリティに影響を与える可能性があります。スタックトランポリンは実行可能な状態のスタックページを必要とするため、バッファオーバーフロー攻撃に対するアプリケーションの脆弱性が高くなります。ネストされた関数のサポートを安全に提供することは不可能だったため、このサポートはXcode 2.2のコンパイラから削除されました。さらに、ネストされた関数をコードから排除すれば、多くの場合パフォーマンスの向上につながります。 先頭に戻る  アプリケーションの変更Xcodeは、アプリケーションをビルドしようとするときに、ネストされたすべての関数をエラーメッセージで示します。これらの関数のネストを解除するには、ネストされた関数をすべて拾い出し、それが属している関数本体の外かつ手前に出します。アプリケーション内の他の関数との衝突を避けるために、ネストされていた関数の名前を変更しなければならない場合もあります。たとえば、次のように変更します。変更前:
void x() {
void y() { ... }
}
変更後:
void y() { ... }
void x() { ... }
変更したらアプリケーションをもう一度ビルドしてみます。ビルドが成功すれば、必要な作業はこれだけです。一方、ネストされていた関数の中で、宣言されていない変数についてエラーメッセージが表示される可能性があります(たとえば、「error: 'foo' undeclared (first use in this function.)」)。これはネストされていた関数が、その親関数のローカル変数にアクセスしている場合に起こります。この場合は、変数を新規パラメータとして関数に追加します。関数で値を変更する可能性があるため、変数はポインタを使用して参照渡しする必要があります。たとえば、次のように変更します。変更前:
void x(int a) {
int b;
void y(int c) { ... }
y(1);
}
変更後:
void y(int c, int *a, int *b) { ... }
void x(int a) {
int b;
y(1, &a, &b);
}
ネストされていた関数を更新し、aとbが単純な整数ではなく整数へのポインタになったことを考慮するようにしてください。 現時点では新しい関数のパラメータになっている、元の関数のローカル変数を、2つの関数がどのように操作するか知っていれば、いくつか最適化できることがあります。ネストされていた関数の中でこれらの変数の値が変わっても、外側の関数でそのことにまったく依存していない場合は、新しい関数に値を参照渡しする必要はありません。つまり先の例で言えば、yにおけるaやbは、このような事実がわかっていれば“int *”ではなく“int”型にできます。内側にあった関数で値を変更することがなければ、“const int a”のような“const”型修飾子を追加できます。 先頭に戻る  別の例リスト1:ネストされた関数を含まない例
int factorial(int num) {
int total = 1, b;
int multiply() {
return total * b;
}
int updateTotal() {
total = multiply();
}
for(b = 1; b <= num; b++) {
updateTotal();
}
return total;
}
リスト2:ネストされた関数を含む例
// multiplyではtotalもbも変更しないため、
// ポインタを使用して渡す必要はない。
// また、constにすることもできる。
int multiply(const int total, const int b) {
return total * b;
}
// updateTotalはbを変更しないが、
// totalを変更する。
int updateTotal(int *total, const int b) {
*total = multiply(*total, b);
}
int factorial(int num) {
int total = 1, b;
for(b = 1; b <= num; b++) {
updateTotal(&total, b);
}
return total;
}
先頭に戻る  ネストされた関数とGNU AutoconfGNU Autoconfを使用するアプリケーション(主として、クロスプラットフォームのUNIXアプリケーション)の場合、設定スクリプトの中でネストされた関数が予期しないところで使用されていることがあり、検出するのが困難です。ネストされた関数のこのように使用されていると、設定チェックが気づかれずに失敗し、正しくない方法でビルドされるアプリケーションが作成されます。 アプリケーションの作成にGNU Autoconfを使用している場合は、Xcode 2.2をインストールしてから設定スクリプトを実行し、“nested functions are not supported”という文字列をconfig.logの中で検索するようにします。文字列が見つかった場合、設定マクロに誤りがあるので正しく調整する必要があります。問題となるマクロの例を示します。
AC_TRY_COMPILE([
#include <stdio.h>
], [
int main(int argc, char *argv[]) {
printf("Hello, world!\n");
return 0;
}
], ...
GNU Autoconfでは設定のテストのために独自のmain関数が用意されています。したがって、マクロを次のように書き直す必要があります。
AC_TRY_COMPILE([
#include <stdio.h>
], [
printf("Hello, world!\n");
], ...
先頭に戻る  ネストされた関数に対するリンカのサポートリンカには“-allow_stack_execute”というスイッチがあります。このスイッチは、スタックセグメントの実行がデフォルトで無効になるように設定されていても、強制的にそれを有効にします。“-fnested-functions”の指定が可能なコンパイラでは、“-fnested-functions”が渡されると、リンカに対して“-allow_stack_execute”を指定します。 先頭に戻る  ドキュメント改訂履歴| 日付 | メモ |
|---|
| 2006-01-10 | このテクニカルノートでは、Xcode 2.2でネストされた関数を使ったコードの移植方法を説明しています。 |
掲載日: 2006-01-10
|