*
1 つの変数で許容されるポインタ ( ) の数は?
次の例を考えてみましょう。
int a = 10;
int *p = &a;
同様に、
int **q = &p;
int ***r = &q;
等々。
例えば、
int ****************zz;
*
1 つの変数で許容されるポインタ ( ) の数は?
次の例を考えてみましょう。
int a = 10;
int *p = &a;
同様に、
int **q = &p;
int ***r = &q;
等々。
例えば、
int ****************zz;
C
標準では下限が指定されています。
5.2.4.1 翻訳の制限
276 実装は、次の制限のすべての少なくとも 1 つのインスタンスを含む少なくとも 1 つのプログラムを変換および実行できなければなりません: [...]
279 — 宣言内の算術、構造体、共用体、または void 型を変更する 12 個のポインター、配列、および関数宣言子 (任意の組み合わせ)
上限は実装固有です。
実際、C プログラムは通常、無限ポインター間接参照を使用します。1 つまたは 2 つの静的レベルが一般的です。トリプル間接化はまれです。しかし、無限は非常に一般的です。
無限ポインタの間接化は、構造体の助けを借りて実現されます。もちろん、直接宣言子を使用することは不可能ですが、これは不可能です。また、この構造体が終了できるさまざまなレベルで、この構造体に他のデータを含めることができるように、構造体が必要です。
struct list { struct list *next; ... };
今、あなたは持つことができますlist->next->next->next->...->next
。これは、実際には単なる複数のポインタの間接化です: *(*(..(*(*(*list).next).next).next...).next).next
. が構造体の最初のメンバーである場合、.next
は基本的に noop であるため、これを として想像でき***..***ptr
ます。
このような巨大な表現ではなく、ループでリンクをたどることができるため、これに制限はありません。さらに、構造は簡単に円形にすることができます。
したがって、言い換えれば、リンクされたリストは、問題を解決するために別のレベルの間接性を追加する究極の例かもしれません。プッシュ操作ごとに動的に実行しているためです。:)
理論的には:
必要な数の間接参照レベルを持つことができます。
特に:
もちろん、メモリを無期限に消費するものはありません。ホスト環境で利用できるリソースによって制限があります。したがって、実際には、実装がサポートできるものには上限があり、実装はそれを適切に文書化する必要があります。そのため、そのような成果物すべてにおいて、規格は上限を指定していませんが、下限を指定しています。
参照は次のとおりです。
C99 標準 5.2.4.1 翻訳制限:
— 宣言内の算術、構造体、共用体、または void 型を変更する 12 個のポインター、配列、および関数宣言子 (任意の組み合わせ)。
これは、すべての実装がサポートしなければならない下限を指定します。脚注で、規格はさらに次のように述べていることに注意してください。
18) 実装は、可能な限り固定の翻訳制限を課すことを避けるべきです。
人々が言ったように、「理論的には」制限はありません。しかし、興味があったので、これを g++ 4.1.2 で実行したところ、最大 20,000 のサイズで動作しました。ただし、コンパイルはかなり遅かったので、それ以上は試しませんでした。したがって、g ++も制限を課していないと思います。size = 10
(すぐにわからない場合は、ptr.cpp を設定して調べてみてください。)
g++ create.cpp -o create ; ./create > ptr.cpp ; g++ ptr.cpp -o ptr ; ./ptr
create.cpp
#include <iostream>
int main()
{
const int size = 200;
std::cout << "#include <iostream>\n\n";
std::cout << "int main()\n{\n";
std::cout << " int i0 = " << size << ";";
for (int i = 1; i < size; ++i)
{
std::cout << " int ";
for (int j = 0; j < i; ++j) std::cout << "*";
std::cout << " i" << i << " = &i" << i-1 << ";\n";
}
std::cout << " std::cout << ";
for (int i = 1; i < size; ++i) std::cout << "*";
std::cout << "i" << size-1 << " << \"\\n\";\n";
std::cout << " return 0;\n}\n";
return 0;
}
チェックするのは楽しそうですね。
Visual Studio 2010 (Windows 7) では、このエラーが発生する前に 1011 レベルを使用できます。
致命的なエラー C1026: パーサー スタック オーバーフロー、プログラムが複雑すぎます
*
gcc (Ubuntu)、クラッシュなしで 100k+ ! ここではハードウェアが限界だと思います。
(変数宣言だけでテスト済み)
制限はありません。ここで例を確認してください。
答えは、「ポインターのレベル」が何を意味するかによって異なります。「1 つの宣言で何レベルの間接参照を使用できますか?」という意味であれば、答えは「少なくとも 12」です。
int i = 0;
int *ip01 = & i;
int **ip02 = & ip01;
int ***ip03 = & ip02;
int ****ip04 = & ip03;
int *****ip05 = & ip04;
int ******ip06 = & ip05;
int *******ip07 = & ip06;
int ********ip08 = & ip07;
int *********ip09 = & ip08;
int **********ip10 = & ip09;
int ***********ip11 = & ip10;
int ************ip12 = & ip11;
************ip12 = 1; /* i = 1 */
「プログラムが読みにくくなる前に、ポインターを何レベル使用できるか」という意味であれば、それは好みの問題ですが、限界があります。2 つのレベルの間接化 (何かへのポインターへのポインター) を持つことは一般的です。それ以上になると、簡単に考えるのが少し難しくなります。代替手段がより悪い場合を除き、それをしないでください。
「実行時に何レベルのポインター間接化を使用できるか」という意味であれば、制限はありません。この点は、各ノードが次のノードを指す循環リストでは特に重要です。あなたのプログラムは永遠にポインターをたどることができます。
関数へのポインターを使用すると、実際にはさらに面白くなります。
#include <cstdio>
typedef void (*FuncType)();
static void Print() { std::printf("%s", "Hello, World!\n"); }
int main() {
FuncType const ft = &Print;
ft();
(*ft)();
(**ft)();
/* ... */
}
ここに示すように、次のようになります。
こんにちは世界!
こんにちは世界!
こんにちは世界!
また、実行時のオーバーヘッドが発生しないため、コンパイラがファイルをチョークするまで、おそらく必要なだけスタックできます。
制限はありません。ポインタは、内容がアドレスであるメモリのチャンクです。
あなたが言ったように
int a = 10;
int *p = &a;
ポインターへのポインターは、別のポインターのアドレスを含む変数でもあります。
int **q = &p;
q
のアドレスを保持しているポインタへのポインタは、 のアドレスをp
すでに保持していますa
。
ポインターへのポインターについて特に特別なことは何もありません。
そのため、別のポインタのアドレスを保持しているポニターのチェーンに制限はありません。
すなわち。
int **************************************************************************z;
許可されています。
ここでは、2つの可能な質問があることに注意してください。Cタイプで達成できるポインター間接参照のレベルと、単一の宣言子に詰め込むことができるポインター間接参照のレベルです。
C規格では、前者に最大値を課すことができます(そしてその最小値を示します)。しかし、これは複数のtypedef宣言によって回避できます。
typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */
したがって、最終的には、これは、Cプログラムが拒否される前に、Cプログラムをどのように大きく/複雑にすることができるかという考えに関連する実装の問題であり、これは非常にコンパイラ固有です。
任意の数の * を持つ型を生成することは、テンプレート メタプログラミングで発生する可能性があることを指摘したいと思います。何をしていたのか正確には忘れてしまいましたが、再帰的なT* 型を使用することで、それらの間である種のメタ操作を行う新しい個別の型を作成できることが示唆されました。
テンプレート メタプログラミングはゆっくりと狂気に陥っていくため、数千レベルの間接性を持つ型を生成するときに言い訳をする必要はありません。たとえば、peano 整数を関数型言語としてテンプレート展開にマップするための便利な方法です。
2004 MISRA C標準の規則 17.5では、2 レベルを超えるポインター間接化が禁止されています。