単一の変数で許可されるポインター(*
)はいくつですか?
次の例を考えてみましょう。
int a = 10;
int *p = &a;
同様に
int **q = &p;
int ***r = &q;
等々。
例えば、
int ****************zz;
C
標準は下限を指定します:
5.2.4.1翻訳制限
276実装は、次の制限のすべてのインスタンスを少なくとも1つ含む少なくとも1つのプログラムを変換して実行できるものとします。[...]
279 — 12個のポインター、配列、および関数宣言子(任意の組み合わせ)が、宣言内の算術、構造、共用体、またはvoid型を変更します
上限は実装固有です。
実際、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翻訳制限:
— 12個のポインター、配列、および関数宣言子(任意の組み合わせ)で、宣言内の算術、構造、共用体、またはvoid型を変更します。
これは、すべての実装mustがサポートする下限を指定します。脚注ではさらに標準が次のように述べていることに注意してください。
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 + *
クラッシュなし!ここでハードウェアが限界だと思います。
(変数宣言のみでテスト済み)
制限はありません。例を確認してください here 。
答えは、「ポインターのレベル」の意味によって異なります。 「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)();
/* ... */
}
図のように here これは以下を与えます:
こんにちは世界!
こんにちは世界!
こんにちは世界!
また、ランタイムのオーバーヘッドが発生しないため、コンパイラーがファイルをチョークするまで、必要なだけスタックすることができます。
制限なしがあります。ポインターは、内容がアドレスであるメモリーの塊です。
あなたが言ったように
int a = 10;
int *p = &a;
ポインターへのポインターは、別のポインターのアドレスを含む変数でもあります。
int **q = &p;
ここで、q
は、既にp
のアドレスを保持しているa
のアドレスを保持するポインターへのポインターです。
ポインターへのポインターについて特に特別なものはありません。
したがって、別のポインターのアドレスを保持しているponitersのチェーンに制限はありません。
ie。
int **************************************************************************z;
許可されています。
ここで2つの可能な質問があることに注意してください。C型で達成できるポインター間接化のレベル数と、単一の宣言子に詰め込めるポインター間接化のレベル数です。
C規格では、前者に最大値を課すことができます(そして、その最小値が与えられます)。しかし、それは複数のtypedef宣言を介して回避できます:
typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */
したがって、最終的に、これは拒否される前にCプログラムをどれだけ大きく/複雑にすることができるかというアイデアに関連する実装の問題であり、これは非常にコンパイラ固有です。
すべてのC++開発者は、(有名な) スリースタープログラマー
そして、偽装する必要のある魔法の「ポインターバリア」があるようです。
C2からの引用:
Three Star Programmer
Cプログラマーの評価システム。ポインターが間接的であればあるほど(つまり、変数の前の「*」が多いほど)、評判は高くなります。実質的にすべての重要なプログラムがポインターの使用を必要とするため、ノースターCプログラマーは実質的に存在しません。ほとんどは一流のプログラマーです。昔は(まあ、私は若いので、少なくとも私には昔のように見えます)、3つ星のプログラマーがdone敬の念を抱いて振る舞うコードを見つけることがあります。一部の人々は、複数レベルのインダイレクションで、関数ポインターが関係する3つ星のコードを見たとさえ主張しました。私にはUFOのようにリアルに聞こえました。
任意の数の*を持つ型を生成することは、テンプレートのメタプログラミングで発生する可能性があることを指摘したいと思います。私が正確にやっていたことを忘れてしまいましたが、recursive T *タイプを使用することで、何らかのメタ操作を行う新しい特殊タイプを作成できることが示唆されました。
テンプレートメタプログラミングは狂気へとゆっくりと下降するため、数千レベルの間接参照を持つ型を生成するときに言い訳をする必要はありません。たとえば、関数型言語としてのテンプレート展開にペアノ整数をマッピングするのに便利な方法です。
2004年の規則17.5MISRA C 標準では、2レベル以上のポインター間接参照が禁止されています。
本当の制限のようなものはありませんが、制限は存在します。すべてのポインターは、通常スタックに格納される変数ですヒープではなく。スタックは通常小さいです(リンク中にサイズを変更することは可能です)。したがって、4MBのスタックがあるとしましょう。これは通常のサイズです。そして、4バイトのサイズのポインターがあるとしましょう(ポインターのサイズは、アーキテクチャー、ターゲット、およびコンパイラーの設定によって異なります)。
この場合は4 MB / 4 b = 1024
なので、可能な最大数は1048576になりますが、他の要素がスタックにあるという事実を無視するべきではありません。
ただし、一部のコンパイラにはポインターチェーンの最大数がありますが、制限はスタックサイズです。したがって、無限とのリンク中にスタックサイズを増やし、そのメモリを処理するOSを実行する無限メモリを備えたマシンを使用すると、ポインタチェーンが無制限になります。
int *ptr = new int;
を使用してポインターをヒープに配置すると、そうではありませんusualウェイ制限はスタックではなくヒープサイズになります。
EDITただinfinity / 2 = infinity
であることを理解してください。マシンのメモリが多いため、ポインターのサイズが大きくなります。メモリが無限で、ポインタのサイズが無限である場合、それは悪いニュースです... :)
ポインタを保存する場所に依存します。それらがスタックにある場合、かなり低い制限があります。ヒープに格納すると、制限ははるかに高くなります。
このプログラムを見てください:
#include <iostream>
const int CBlockSize = 1048576;
int main()
{
int number = 0;
int** ptr = new int*[CBlockSize];
ptr[0] = &number;
for (int i = 1; i < CBlockSize; ++i)
ptr[i] = reinterpret_cast<int *> (&ptr[i - 1]);
for (int i = CBlockSize-1; i >= 0; --i)
std::cout << i << " " << (int)ptr[i] << "->" << *ptr[i] << std::endl;
return 0;
}
それは1Mのポインターを作成し、チェーンが最初の変数number
に行くものに気付くのが簡単なものへのポイントを示します。
ところで。 RAMの92K
を使用しているので、どれだけ深くできるか想像してみてください。