「名前修飾」の版間の差分
編集の要約なし |
|||
10行目: | 10行目: | ||
次のCコードをコンパイルするとしよう: |
次のCコードをコンパイルするとしよう: |
||
< |
<syntaxhighlight lang="c"> |
||
int _cdecl f(int x) { return 0; } |
int _cdecl f(int x) { return 0; } |
||
int _stdcall g(int y) { return 0; } |
int _stdcall g(int y) { return 0; } |
||
int _fastcall h(int z) { return 0; } |
int _fastcall h(int z) { return 0; } |
||
</syntaxhighlight> |
|||
</source> |
|||
<code>[[呼出規約#cdecl|_cdecl]]</code> はCの標準の呼び出し規約を使うことを明示する修飾子である。 |
<code>[[呼出規約#cdecl|_cdecl]]</code> はCの標準の呼び出し規約を使うことを明示する修飾子である。 |
||
36行目: | 36行目: | ||
C++における<tt>f()</tt>の、次の二つの定義をみてみよう: |
C++における<tt>f()</tt>の、次の二つの定義をみてみよう: |
||
< |
<syntaxhighlight lang="cpp"> |
||
int f (void) { return 1; } |
int f (void) { return 1; } |
||
int f (int) { return 0; } |
int f (int) { return 0; } |
||
void g (void) { int i = f(), j = f(0); } |
void g (void) { int i = f(), j = f(0); } |
||
</syntaxhighlight> |
|||
</source> |
|||
この二つは異なった関数である。名前以外に全く関係がない。馬鹿正直にこれをCに変換すると、Cコンパイラはエラーを吐く — 関数の名前の重複は許されないからである。そこで、C++コンパイラはシンボル名に型情報を加える。例えばこんな風になるだろう: |
この二つは異なった関数である。名前以外に全く関係がない。馬鹿正直にこれをCに変換すると、Cコンパイラはエラーを吐く — 関数の名前の重複は許されないからである。そこで、C++コンパイラはシンボル名に型情報を加える。例えばこんな風になるだろう: |
||
< |
<syntaxhighlight lang="c"> |
||
int __f_v (void) { return 1; } |
int __f_v (void) { return 1; } |
||
int __f_i (int) { return 0; } |
int __f_i (int) { return 0; } |
||
void __g_v (void) { int i = __f_v(), j = __f_i(0); } |
void __g_v (void) { int i = __f_v(), j = __f_i(0); } |
||
</syntaxhighlight> |
|||
</source> |
|||
ここで、<tt>g()</tt>は名前の重複問題がないのに修飾されていることに注意して欲しい。修飾は全てのシンボルに適用される。 |
ここで、<tt>g()</tt>は名前の重複問題がないのに修飾されていることに注意して欲しい。修飾は全てのシンボルに適用される。 |
||
55行目: | 55行目: | ||
もっと複雑な例を挙げる。実際に用いられている名前修飾を見てみよう。GNU GCC 3.''x''は次のクラス例をどのように修飾するだろうか。修飾されたシンボルはそれぞれの識別子の下に表示されている。 |
もっと複雑な例を挙げる。実際に用いられている名前修飾を見てみよう。GNU GCC 3.''x''は次のクラス例をどのように修飾するだろうか。修飾されたシンボルはそれぞれの識別子の下に表示されている。 |
||
< |
<syntaxhighlight lang="cpp"> |
||
namespace wikipedia { |
namespace wikipedia { |
||
class article { |
class article { |
||
72行目: | 72行目: | ||
}; |
}; |
||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
ここでの名前修飾スキームは比較的単純である。修飾された名前は全て '''_Z''' で始まる。下線に大文字を続けたものはCおよびC++では予約語であることに注意されたい。従って、ユーザの識別子とぶつかることはない。[[ネスト]]された名前は(名前空間、クラス両者)、<tt>'''N'''</tt>をつけ、次いで <長さ,id> のペアをつけて示す(「長さ」は次の識別子の長さ)。最後に<tt>'''E'''</tt>を付ける。例えば、<tt>wikipedia::article::format</tt>は |
ここでの名前修飾スキームは比較的単純である。修飾された名前は全て '''_Z''' で始まる。下線に大文字を続けたものはCおよびC++では予約語であることに注意されたい。従って、ユーザの識別子とぶつかることはない。[[ネスト]]された名前は(名前空間、クラス両者)、<tt>'''N'''</tt>をつけ、次いで <長さ,id> のペアをつけて示す(「長さ」は次の識別子の長さ)。最後に<tt>'''E'''</tt>を付ける。例えば、<tt>wikipedia::article::format</tt>は |
||
194行目: | 194行目: | ||
次のようなよくあるC++の例 |
次のようなよくあるC++の例 |
||
< |
<syntaxhighlight lang="cpp"> |
||
#ifdef __cplusplus |
#ifdef __cplusplus |
||
extern "C" { |
extern "C" { |
||
202行目: | 202行目: | ||
} |
} |
||
#endif |
#endif |
||
</syntaxhighlight> |
|||
</source> |
|||
は、引き続くシンボルを修飾しないことを指示する。すなわち、コンパイラはあたかもCコンパイラであるかのように、修飾なしの名前を用いたバイナリを吐く。Cが名前修飾を利用していないので、C++コンパイラもそれらの識別子を参照する際名前修飾を避けなければならない。 |
は、引き続くシンボルを修飾しないことを指示する。すなわち、コンパイラはあたかもCコンパイラであるかのように、修飾なしの名前を用いたバイナリを吐く。Cが名前修飾を利用していないので、C++コンパイラもそれらの識別子を参照する際名前修飾を避けなければならない。 |
||
208行目: | 208行目: | ||
例として、標準的な文字列ライブラリ <tt><string.h></tt> は、通常次のようなコードを含む: |
例として、標準的な文字列ライブラリ <tt><string.h></tt> は、通常次のようなコードを含む: |
||
< |
<syntaxhighlight lang="cpp"> |
||
#ifdef __cplusplus |
#ifdef __cplusplus |
||
extern "C" { |
extern "C" { |
||
221行目: | 221行目: | ||
} |
} |
||
#endif |
#endif |
||
</syntaxhighlight> |
|||
</source> |
|||
そこで、次のコード |
そこで、次のコード |
||
< |
<syntaxhighlight lang="cpp"> |
||
if (strcmp(argv[1], "-x") == 0) |
if (strcmp(argv[1], "-x") == 0) |
||
strcpy(a, argv[2]); |
strcpy(a, argv[2]); |
||
else |
else |
||
memset(a, 0, sizeof(a)); |
memset(a, 0, sizeof(a)); |
||
</syntaxhighlight> |
|||
</source> |
|||
は、正しい、修飾されない <tt>strcmp</tt> 及び <tt>memset</tt> を用いることになる。<tt>extern</tt> が用いられなければ、C++コンパイラは同等の次のコードを生成するだろう |
は、正しい、修飾されない <tt>strcmp</tt> 及び <tt>memset</tt> を用いることになる。<tt>extern</tt> が用いられなければ、C++コンパイラは同等の次のコードを生成するだろう |
||
< |
<syntaxhighlight lang="c"> |
||
if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0) |
if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0) |
||
__1cGstrcpy6Fpcpkc_0_(a, argv[2]); |
__1cGstrcpy6Fpcpkc_0_(a, argv[2]); |
||
else |
else |
||
__1cGmemset6FpviI_0_(a, 0, sizeof(a)); |
__1cGmemset6FpviI_0_(a, 0, sizeof(a)); |
||
</syntaxhighlight> |
|||
</source> |
|||
これらのシンボルはCの[[ランタイムライブラリ]](例えば <tt>libc</tt>)には存在しないので、リンカはエラーを報告することになる。 |
これらのシンボルはCの[[ランタイムライブラリ]](例えば <tt>libc</tt>)には存在しないので、リンカはエラーを報告することになる。 |
||
266行目: | 266行目: | ||
無名の (anonymous) クラスの[[スコープ]]は、その親クラスに制限される。そのため、コンパイラは「修飾付きの」 (qualified) パブリックな名前を内部クラスに対して与えなければならない。同様に無名のクラスには「偽の」パブリックな名前を生成しなければならない(無名クラスはコンパイラの概念であり、実行時には関係がない)。そこで、次のJavaプログラムをコンパイルすると |
無名の (anonymous) クラスの[[スコープ]]は、その親クラスに制限される。そのため、コンパイラは「修飾付きの」 (qualified) パブリックな名前を内部クラスに対して与えなければならない。同様に無名のクラスには「偽の」パブリックな名前を生成しなければならない(無名クラスはコンパイラの概念であり、実行時には関係がない)。そこで、次のJavaプログラムをコンパイルすると |
||
< |
<syntaxhighlight lang="java"> |
||
public class Foo { |
public class Foo { |
||
class bar { |
class bar { |
||
280行目: | 280行目: | ||
} |
} |
||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
三つの '''.class''' ファイルが生成される。 |
三つの '''.class''' ファイルが生成される。 |
||
297行目: | 297行目: | ||
[[Python]]のプログラマは識別子の最初の二文字を下線にすることで、明示的にそれが「プライベートな名前」(スコープがクラスに限られる)であることを示すことができる。コンパイラはこれらを見るとプライベートな名前を大域的なシンボルに置き換える。これは、その識別子を包含するクラスの名前を一個の下線とともに前づけして行われる。例えば、 |
[[Python]]のプログラマは識別子の最初の二文字を下線にすることで、明示的にそれが「プライベートな名前」(スコープがクラスに限られる)であることを示すことができる。コンパイラはこれらを見るとプライベートな名前を大域的なシンボルに置き換える。これは、その識別子を包含するクラスの名前を一個の下線とともに前づけして行われる。例えば、 |
||
< |
<syntaxhighlight lang="python"> |
||
class Test: |
class Test: |
||
def __privateSymbol(self): |
def __privateSymbol(self): |
||
305行目: | 305行目: | ||
print dir(Test) |
print dir(Test) |
||
</syntaxhighlight> |
|||
</source> |
|||
はつぎのようになる |
はつぎのようになる |
2020年7月5日 (日) 22:47時点における版
名前修飾(なまえしゅうしょく、name mangling)は、現代的なコンピュータプログラミング言語処理系で用いられている手法で、サブルーチン(関数)名などに対する内部名を、その表層的な名前のみならず、関数であればその引数の型や返戻値の型などといった意味的な情報を含めて修飾した(manglingした)名前とするものである。コンパイラからリンカ、さらには実行時のデバッガなども含んだシステム全体が、高度な型に関する情報などをサポートするように再実装するには多くの難しさがあるが、この手法であれば、システムの多くの部分ではわずかな修正(たとえば、名前に '$' という文字が含まれることを許すようにする、など)で済む。特に、多重定義を許す言語では、同一の表層名に対して許される多重定義や、その同定について上手に修飾を設計すれば、扱いが単純になる。また、そのままではエラーメッセージ等が読み辛いものとなるが、「解読」ルーチンを呼ぶように修正するだけで、型の情報などが付加された、むしろわかりやすいメッセージが出力されるようになる。
Microsoft Windowsの場合
一般的なC・Pascalなどの言語は関数の多重定義をサポートせず、名前修飾を必要としないが、場合によっては名前修飾によって関数についての情報を付加することがある。
例えば、Microsoft Windows 上のコンパイラは複数の呼出規約(サブルーチンとデータをやりとりする方法)をサポートしている。呼出規約の間には互換性がないので、コンパイラは名前修飾によって呼出規約を詳細に記述する。
MSによって確立された名前修飾のスキームがあり、非公式に他のコンパイラもこれに従っている。例えば、Digital Mars・ボーランド・gccである。このスキームは他の言語、例えば、Pascal・D言語・Delphi・FORTRAN・C#にも適用される。このようにして、それら処理系のデフォルトの呼出規約が異なる場合も、それら処理系で作成したサブルーチンが現存のWindows ライブラリを呼んだり、そこから呼ばれたりすることができる。
次のCコードをコンパイルするとしよう:
int _cdecl f(int x) { return 0; }
int _stdcall g(int y) { return 0; }
int _fastcall h(int z) { return 0; }
_cdecl
はCの標準の呼び出し規約を使うことを明示する修飾子である。
32 bit コンパイラはそれぞれ、以下を出力する。
_f _g@4 @h@4
stdcallとfastcallでは、関数名は_名前@X と @名前@Xのようにエンコードされる。Xにはコールスタック(以下単にスタック)に積まれる引数のバイト数が入る。
他の一般的な修飾法は、前置句 (prefix) を、下線付きで(__func__のように)あるいは標準的な大文字化を行ったりして加えるものである。
C++の場合
名前修飾を行う処理系のうち、C++コンパイラは最も広く用いられているが、最も標準化が進んでいないものである。最初のC++コンパイラはCソースコードへのトランスレータとして実装された。そのため、シンボルの名前はCの識別子の規則に従う必要があった。後にC++コンパイラ自身が機械語コードやアセンブラコードを出力するようになっても、計算機システムのリンカは総じてC++のシンボルをサポートせず、名前修飾が必要な状態が続いた。
C++言語は、標準的な修飾規則を定めていない。そこで、コンパイラによって修飾規則が異なる。C++の修飾がかなり複雑になりうること(クラス、デフォルトの引数、変数のオーナー、演算子オーヴァーロードなどの情報を格納する)も加わり、異なるコンパイラのオブジェクトコードはリンクすることができないのが通常である。
簡単な例
C++におけるf()の、次の二つの定義をみてみよう:
int f (void) { return 1; }
int f (int) { return 0; }
void g (void) { int i = f(), j = f(0); }
この二つは異なった関数である。名前以外に全く関係がない。馬鹿正直にこれをCに変換すると、Cコンパイラはエラーを吐く — 関数の名前の重複は許されないからである。そこで、C++コンパイラはシンボル名に型情報を加える。例えばこんな風になるだろう:
int __f_v (void) { return 1; }
int __f_i (int) { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }
ここで、g()は名前の重複問題がないのに修飾されていることに注意して欲しい。修飾は全てのシンボルに適用される。
複雑な例
もっと複雑な例を挙げる。実際に用いられている名前修飾を見てみよう。GNU GCC 3.xは次のクラス例をどのように修飾するだろうか。修飾されたシンボルはそれぞれの識別子の下に表示されている。
namespace wikipedia {
class article {
public:
std::string format (void);
''/* = _ZN9wikipedia7article6formatEv */''
bool print_to (std::ostream&);
''/* = _ZN9wikipedia7article8print_toERSo */''
class wikilink {
public:
wikilink (std::string const& name);
''/* = _ZN9wikipedia7article8wikilinkC1ERKSs */''
};
};
}
ここでの名前修飾スキームは比較的単純である。修飾された名前は全て _Z で始まる。下線に大文字を続けたものはCおよびC++では予約語であることに注意されたい。従って、ユーザの識別子とぶつかることはない。ネストされた名前は(名前空間、クラス両者)、Nをつけ、次いで <長さ,id> のペアをつけて示す(「長さ」は次の識別子の長さ)。最後にEを付ける。例えば、wikipedia::article::formatは
_ZN·9wikipedia·7article·6format·E
となる。
関数の場合は、続いて型情報が付加される。format()は void 関数なので、単にvを付ける。よって、
_ZN·9wikipedia·7article·6format·E·v
となる。
print_toの場合は、標準的な型として std::ostream (あるいは、もっと正確には std::basic_ostream<char, char_traits<char> >)が用いられ、これには特殊な別名 So がある。よって、この型に対する参照はRSoとなる。名前の完成形はこうなる。
_ZN·9wikipedia·7article·8print_to·E·RSo
コンパイラによる名前修飾の相違
C++では、ささいな識別子ですら、名前修飾の標準スキームは存在しない。そのため、コンパイラベンダによって、あるいは同じコンパイラでも版によって、更に場合によっては同じ版でもプラットフォームによって全く異なった、互換性のない方法をとることになる。同じ関数について、その違いを見てみよう。
コンパイラ | void h(int) | void h(int, char) | void h(void) |
---|---|---|---|
clang 1.x | _Z1hi | _Z1hic | _Z1hv |
GNU GCC 3.x | _Z1hi | _Z1hic | _Z1hv |
GNU GCC 2.9x | h__Fi | h__Fic | h__Fv |
Intel C++ 8.0 for Linux | _Z1hi | _Z1hic | _Z1hv |
Microsoft VC++ v6/v7 | ?h@@YAXH@Z | ?h@@YAXHD@Z | ?h@@YAXXZ |
Borland C++ v3.1 | @h$qi | @h$qizc | @h$qv |
OpenVMS C++ V6.5 (ARM mode) | H__XI | H__XIC | H__XV |
OpenVMS C++ V6.5 (ANSI mode) | CXX$__7H__FI0ARG51T | CXX$__7H__FIC26CDH77 | CXX$__7H__FV2CB06E8 |
OpenVMS C++ X7.1 IA-64 | CXX$_Z1HI2DSQ26A | CXX$_Z1HIC2NP3LI4 | CXX$_Z1HV0BCA19V |
Digital Mars C++ | ?h@@YAXH@Z | ?h@@YAXHD@Z | ?h@@YAXXZ |
SunPro CC | __1cBh6Fi_v_ | __1cBh6Fic_v_ | __1cBh6F_v_ |
HP aC++ A.05.55 IA-64 | _Z1hi | _Z1hic | _Z1hv |
HP aC++ A.03.45 PA-RISC | h__Fi | h__Fic | h__Fv |
Tru64 C++ V6.5 (ARM mode) | h__Xi | h__Xic | h__Xv |
Tru64 C++ V6.5 (ANSI mode) | __7h__Fi | __7h__Fic | __7h__Fv |
註:
- OpenVMS VAX、DEC Alpha(IA-64を除く)及び Tru64 UNIX 上のCompaq C++ コンパイラは二種類の修飾スキームを持っている。標準化前のもともとのスキームは ARM モデルとして知られていた。これは『The Annotated C++ Reference Manual (ARM)』(邦訳『注解C++リファレンスマニュアル』)に記述された方法を元にしている。標準C++の機能拡充、特にテンプレート機能の追加に伴い、ARM は次第に旧式化していった — ある種の関数型をエンコードできず、異なった関数に異なったシンボル名を割り当てることができなくなっていた。そこで、より新しい ANSI モデルが導入され、ANSIテンプレート機能が全て利用可能になったが、過去の版との互換性は失われた。todo: the different isn't obvious from the examples. maybe a template or something should be added...
- IA-64にはApplication Binary Interface標準の名前修飾規則(Itanium C++ ABI mangling)が存在する(#外部リンク参照)。これは標準的な名前修飾スキームを定義したものであり、全てのIA-64コンパイラで利用されている。加えて、GNU GCC 3.xもこの標準を用いている。インテル環境以外でも利用することができる。
C++からリンクする際の C シンボルの扱い
次のようなよくあるC++の例
#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif
は、引き続くシンボルを修飾しないことを指示する。すなわち、コンパイラはあたかもCコンパイラであるかのように、修飾なしの名前を用いたバイナリを吐く。Cが名前修飾を利用していないので、C++コンパイラもそれらの識別子を参照する際名前修飾を避けなければならない。
例として、標準的な文字列ライブラリ <string.h> は、通常次のようなコードを含む:
#ifdef __cplusplus
extern "C" {
#endif
void *memset (void *, int, size_t);
char *strcat (char *, const char *);
int strcmp (const char *, const char *);
char *strcpy (char *, const char *);
#ifdef __cplusplus
}
#endif
そこで、次のコード
if (strcmp(argv[1], "-x") == 0)
strcpy(a, argv[2]);
else
memset(a, 0, sizeof(a));
は、正しい、修飾されない strcmp 及び memset を用いることになる。extern が用いられなければ、C++コンパイラは同等の次のコードを生成するだろう
if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0)
__1cGstrcpy6Fpcpkc_0_(a, argv[2]);
else
__1cGmemset6FpviI_0_(a, 0, sizeof(a));
これらのシンボルはCのランタイムライブラリ(例えば libc)には存在しないので、リンカはエラーを報告することになる。
C++での名前修飾の標準化
C++で名前修飾の標準化を行うと、実装をまたいだ運用がしやすくなるというのが比較的広く信じられているが、これは実際には正しくない。名前修飾はApplication Binary Interface (ABI) や他の細かな言語仕様(例外処理、仮想テーブルのレイアウト、構造体のパディングなど)におけるいくつかの問題の一つに過ぎず、名前修飾だけをどうかしても非互換性は残ることになる。更に、特定の修飾法を決めてしまうと、実装が制限されるシステムが出現しうる(例えば、シンボルの長さ)。また、名前修飾を標準化してしまうと、例えばC++の文法を理解できるリンカのような、名前修飾を必要としない実装を妨げる可能性もある。
そのため、ISOではC++の標準 (en:ISO/IEC 14882) として、名前修飾を標準化することを特に目指してはいない。逆に、Annotated C++ Reference Manual(ARM の名でも知られる。 ISBN 0-201-51459-1, section 7.2.1c)では、ABI上の他の非互換性を抱えたモジュールを誤ってリンクしないように、異なった名前修飾法を用いることが推奨されている。
C++名前修飾問題の現実的な影響
C++のシンボルはDLLや共有オブジェクトを通してルーチン的にエクスポートされるため、名前修飾スキームはコンパイラの問題だけではすまなくなる。ライブラリをコンパイルするにあたって、複数のコンパイラ(場合によっては、同じコンパイラでも版が異なるだけで問題になりうる)によって名前修飾がそれぞれ異なったスキームで行われると、それらのライブラリを参照する際、しばしばシンボルが解決できなくなってしまう。例えば、複数のC++コンパイラ(例えば、GCCとOS付属のコンパイラ)が導入されているシステムにBoostを導入しようとすると、二度それをコンパイルしなければならない ―― OSベンダのコンパイラとGCCで。
このため、名前修飾はC++が関係した ABIでの重要な側面の一つとなっている。
Java の場合
言語、コンパイラ、.classファイルフォーマットが同時に設計され、また開発当初からオブジェクト指向が取り入れられていたため、名前修飾を必要とするような問題はJava実行時環境の実装には存在しない。しかしながら、これまでに見てきた名前修飾に類似した名前の変換が必要な場合がある。
内部及び無名クラスに一意名を与える
無名の (anonymous) クラスのスコープは、その親クラスに制限される。そのため、コンパイラは「修飾付きの」 (qualified) パブリックな名前を内部クラスに対して与えなければならない。同様に無名のクラスには「偽の」パブリックな名前を生成しなければならない(無名クラスはコンパイラの概念であり、実行時には関係がない)。そこで、次のJavaプログラムをコンパイルすると
public class Foo {
class bar {
public int x;
}
public void zark () {
Object f = new Object () {
public String toString() {
return "hello";
}
};
}
}
三つの .class ファイルが生成される。
- Foo.class, 主クラス(外側のクラス) Foo を含む。
- Foo$bar.class, Foo.bar という名前付きの内部クラスを含む。
- Foo$1.class, メソッド Foo.zark に対して局所的な無名の内部クラスを含む。
ドル記号 ($) はJVMの仕様上許されているので、これら三つのクラス名は全て有効であり、Java言語の仕様上 $ は通常のJavaクラス定義に用いることができないので、コンパイラは安全にこれらの名前を利用することができる。
完全修飾名は特定のクラスローダインスタンスの内部でのみ一意であるので、実行時にはJavaにおける名前の解決は更に複雑である。クラスローダは階層性をもっており、JVMの各スレッドはいわゆる文脈クラスローダ (context class loader) を持っている。そこで、二つの異なったクラスローダインスタンスが同じ名前のクラスを含む時、システムは初めルート(あるいはシステム)クラスローダを用いてクラスをロードしようとし、次いで階層に従って文脈クラスローダをたどる。
Java - ネイティブ間のインターフェースの扱い
Javaのネイティブメソッドサポートによって、Javaで記述されたプログラムから他の言語で書かれたプログラム(通常CまたはC++)を呼ぶことができる。ここでは二つの名前解決が関係するが、'Java Native Interface(JNI)として標準化されている。これとは別に、Java Native Access(JNA)とは、Javaプログラムからネイティブの共有ライブラリにアクセスする方法をライブラリレベルで提供する。
Python の場合
Pythonのプログラマは識別子の最初の二文字を下線にすることで、明示的にそれが「プライベートな名前」(スコープがクラスに限られる)であることを示すことができる。コンパイラはこれらを見るとプライベートな名前を大域的なシンボルに置き換える。これは、その識別子を包含するクラスの名前を一個の下線とともに前づけして行われる。例えば、
class Test:
def __privateSymbol(self):
pass
def normalSymbol(self):
pass
print dir(Test)
はつぎのようになる
['_Test__privateSymbol', '__doc__', '__module__', 'normalSymbol']
Turbo Pascal / Delphi の場合
これらのPascal処理系では、次のようにして名前修飾を抑制する。
exports myFunc name 'myFunc', myProc name 'myProc';
Objective-Cの場合
Objective-Cのメソッドは、本質的に二種類に分けられる。一つは クラス(「静的」)メソッドで、もう一つはインスタンスメソッドである。Objective-Cでのメソッド宣言は次のような形式である。
+ method name: argument name1:parameter1 ... - method name: argument name1:parameter1 ...
クラスメソッドは + で示される。インスタンスメソッドは - で示される。典型的なクラスメソッド宣言は、次のようになるだろう
+ (id) initWithX: (int) number andY: (int) number; + (id) new;
インスタンスメソッドならば、次のようである
- (id) value; - (id) setValue: (id) new_value;
それぞれのメソッド宣言は特有の内部表現を持っている。コンパイル時に、メソッド名は次のスキームによって変換される。クラスメソッドでは
_c_Class_methodname_name1_name2_ ...
となり、インスタンスメソッドでは
_i_Class_methodname_name1_name2_ ...
となる。
Objective-Cのコロンは下線に変換される。そこで、Point クラスに属するクラスメソッド + (id) initWithX: (int) number andY: (int) number; は次のように変換されるだろう _c_Point_initWithX_andY_。同じクラスに属するインスタンスメソッド - (id) value; は _i_Point_value となる。
クラスの各メソッドはこのようにラベルされるが、全てのメソッドがこのように表現された場合、あるクラスが応答すべきメソッドを探し出すのは面倒な作業となりうる。そのため、各々のメソッドに整数のようなシンボルを一意に割り当てる。このようなシンボルは「セレクタ」として知られる。Objective-Cでは、プログラマがセレクタを直接管理することができる — Objective-Cではそれらに特別の型を与えている — SEL。
コンパイル中に、(_i_Point_valueのような)文字による表現からセレクタ(SEL型)へのマップが作成される。文字による表現を操作するよりもセレクタを管理する方がメソッドを効果的に扱うことができる。セレクタがマッチするのはメソッドの名前だけであり、それが属するクラスではないということに注意してほしい。クラスが異なれば同じ名前のメソッドでも実装が異なることがある。このため、メソッドの実装にも特別の識別子が与えられる — 実装ポインタ (implementation pointer) と呼ばれ、IMP 型を持つ。
オブジェクトにメッセージを送ると、それはコンパイラによって、 id objc_msgSend(id receiver, SEL selector, ...) 関数ないしはその従兄弟のどれかに対する呼び出しとしてエンコードされる。ここで、receiverはそのメッセージの受け手であり、SELによって呼び出されるメソッドが決まる。各々のクラスはそれ自身の表を持っており、セレクタと実装 — メソッドの実体が存在するメモリ空間を指定する実装ポインタ — との相互対照ができるようになっている。また別の表にはクラスとインスタンスメソッドが記録される。SELからIMPへの対照表に格納されることはさておき、関数は本質的に無名である。
あるセレクタに対するSELの値はクラスによって変わることがなく、多態性を実現している。
Objective-Cの実行環境はメソッドの引数と返り値の型についての情報を保持しているが、メソッドの名前の一部として保持されるわけではなく、クラスによって変化しうる。
Objective-Cは名前空間をサポートしないので、クラス名を修飾する必要はない。
関連項目
- 呼出規約
- 中間表現
- 名前空間
- 言語束縛
- 可変長引数
- コンピュータ・アーキテクチャ
- オブジェクトファイル
- シンボルテーブル
- コールスタック
- Application Binary Interface
- Foreign function interface
- SWIG
- P/Invoke
- nm (UNIX)
外部リンク
- Itanium C++ ABI Summary
- c++filt — filter to demangle encoded C++ symbols
- Objective-C Runtime Programming Guide
- Visual C++ name mangling - Wikiversity
- Macintosh C/C++ ABI Standard Specification