ポインタにはいくつのレベルがありますか?


443

ポインタの数(*1つの変数でいくつの)を使用できますか?

次の例を考えてみましょう。

int a = 10;
int *p = &a;

同様に、

int **q = &p;
int ***r = &q;

等々。

例えば、

int ****************zz;

582
それがあなたにとって本当の問題になる場合、あなたは何か非常に悪いことをしています。
ThiefMaster 2012

279
脳が爆発するか、コンパイラーが溶けるまで、最も早いレベルでポインターのレベルを追加し続けることができます。
JeremyP 2012

47
ポインタへのポインタもまた、単なるポインタなので、理論上の制限はありません。たぶん、コンパイラーは
途方

73
最新のC ++では、次のようなものを使用する必要がありますstd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx

44
@josefx-これはC ++標準に問題があることを示しています-パワーへのスマートポインターを上げる方法はありません。たとえば(pow (std::shared_ptr, -0.3))<T> x;-0.3レベルの間接参照をサポートする拡張機能をすぐに要求する必要があります。
Steve314 2012

回答:


400

C標準の指定下限:

5.2.4.1翻訳の制限

276実装は、次の制限のすべての少なくとも1つのインスタンスを含む少なくとも1つのプログラムを変換および実行できる必要があります:[...]

279 —宣言の算術型、構造体、共用体、またはvoid型を変更する12個のポインター、配列、および関数宣言子(任意の組み合わせ)

上限は実装固有です。


121
C ++標準は、実装が少なくとも256をサポートすることを「推奨」します。(読みやすさは、2または3を超えないことをお勧めします。それでも、複数であることは例外です。)
James Kanze

22
この制限は、単一の宣言内の数に関するものです。複数typedefのを介して達成できる間接性に上限はありません。
Kaz

11
@カズ-はい、それは本当です。しかし、仕様は必須の下限を指定している(しゃれた意図はない)ため、仕様に準拠するすべてのコンパイラーがサポートする必要がある有効な上限です。もちろん、ベンダー固有の上限よりも低くなる可能性があります。別の言い方をすれば(OPの質問に合わせるため)、これは仕様で許可されている最大値 です(それ以外はベンダー固有です)。少し正直なところ、プログラマー(少なくとも一般的なケースでは)それを上限(ベンダー固有の上限に依存する正当な理由がない限り)...私は考えています。
luis.espinal 2012

5
別の注記では、長い尻の逆参照チェーンを持つコードで作業しなければならなかった場合、私は自分自身を切り始めます(特に、いたるところに自由に
ペッパーを付け

11
@beryllium:通常、これらの数値は事前標準化ソフトウェアの調査から得られたものです。この場合、おそらく彼らは一般的なCプログラムと既存のCコンパイラーを調べ、12を超えると問題が発生するコンパイラーを少なくとも1つ見つけ、12に制限するとプログラムが壊れることはありませんでした

155

実際、Cプログラムは通常、無限ポインターの間接参照を使用します。1つまたは2つの静的レベルが一般的です。三重の間接参照はまれです。しかし、無限は非常に一般的です。

無限ポインターの間接指定は、構造体を使用して実現されます。もちろん、直接宣言子を使用するのではありません。これは不可能です。また、構造体が必要になる可能性があるさまざまなレベルでこの構造体に他のデータを含めることができるように、構造体が必要です。

struct list { struct list *next; ... };

今、あなたは持つことができますlist->next->next->next->...->next。これは実際には単なる複数のポインタの間接化です*(*(..(*(*(*list).next).next).next...).next).next。そして、.next構造体の最初のメンバーの場合、これは基本的に何もしないので、と想像でき***..***ptrます。

リンクはこのような巨大な式ではなくループでトラバースできるため、これに本当に制限はありません。さらに、構造を簡単に円形にすることができます。

したがって、言い換えると、リンクされたリストは、すべてのプッシュ操作で動的に実行しているため、問題を解決するために別のレベルの間接参照を追加する究極の例になる可能性があります。:)


48
ただし、これは完全に異なる問題です。別の構造体へのポインタを含む構造体は、ポインタポインタとは大きく異なります。int *****はint ****とは異なるタイプです。
ふわふわ

12
それは「非常に」異なっていません。違いはふわふわです。セマンティクスよりも構文に近いです。ポインターオブジェクトへのポインター、またはポインターを含む構造オブジェクトへのポインター?同じようなものです。リストの10番目の要素に到達するには、10レベルのアドレッシングインダイレクションがあります。(もちろん、無限の構造を発現する能力は、そのように不完全な構造体型を介して自身にポイントすることができる構造体の種類に依存list->nextし、list->next->next同じタイプであり、そうでなければ、我々は無限の種類を構築しなければならない。)
カズ

34
「ふわふわ」という言葉を使ったとき、あなたの名前がふわふわしていることに気づきませんでした。潜在意識の影響?しかし、私は以前にこの言葉をこのように使ったことがあると思います。
Kaz

3
また、機械語でLOAD R1, [R1]は、R1がすべてのステップで有効なポインタである限り、次のようなことを繰り返すことができます。「住所を保持する単語」以外に関係する種類はありません。宣言された型があるかどうかは、間接指定とそれが持つレベルの数を決定しません。
Kaz

4
構造が円形の場合はそうではありません。R1自身を指す場所のアドレスを保持している場合LOAD R1, [R1]、無限ループで実行できます。
Kaz

83

理論的には:

間接参照のレベルはいくつでも持つことができます。

実際には:

もちろん、メモリを消費するものは不定ではありません。ホスト環境で利用可能なリソースにより制限があります。したがって、実際には実装がサポートできるものには上限があり、実装はそれを適切に文書化する必要があります。したがって、そのようなすべてのアーティファクトでは、標準は最大制限を指定していませんが、下限を指定しています。

ここに参照があります:

C99標準5.2.4.1翻訳制限:

—宣言内の算術型、構造体、共用体、またはvoid型を変更する12個のポインター、配列、および関数宣言子(任意の組み合わせ)。

これは、すべての実装サポートする必要のある下限を指定します。脚注で、標準はさらに次のように述べていることに注意してください:

18)実装では、可能な限り、固定された翻訳制限を課さないようにする必要があります。


16
間接参照はスタックをオーバーフローしません!
バジルStarynkevitch

1
修正すると、関数に渡されるパラメーターの制限として、qを読んだり答えたりするというこのエリーな感覚がありました。なぜか分からない!
Alok Save

2
@basile-スタックの深さがパーサーの問題になると思います。多くの正式な解析アルゴリズムには、主要なコンポーネントとしてスタックがあります。ほとんどのC ++コンパイラは、再帰的降下のバリアントを使用する可能性がありますが、それでもプロセッサスタック(または、厳密には、プロセッサスタックが存在するかのように動作する言語)に依存しています。文法規則のネストが増えるほど、スタックが深くなります。
Steve314 2012

8
間接参照はスタックをオーバーフローしません!->いいえ!パーサースタックがオーバーフローする可能性があります。 スタックはポインタの間接参照とどのように関連していますか?パーサースタック!
Pavan Manjunath

場合は、*行のクラスの数のためにオーバーロードされ、各過負荷が行の他のタイプのオブジェクトを返し、そのような連鎖関数呼び出しのためのstackoverflowのあり得ます。
Nawaz、2012年

76

人々が言っ​​たように、「理論上」制限はありません。しかし、興味があるため、私はこれを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;
}

72
試してみたところ、98242個もありませんでした。(Pythonでスクリプト*を実行し、失敗したものとその前の合格したものを取得するまでの数を倍にしました。その後、失敗した最初の1つについて、その間隔でバイナリ検索を実行しました。テスト全体は1秒未満かかりました実行します。)
James Kanze 2012

63

チェックするのは楽しいですね。

  • Visual Studio 2010(Windows 7)では、このエラーが発生する前に1011レベルが存在する可能性があります。

    致命的なエラーC1026:パーサースタックオーバーフロー、プログラムが複雑すぎます

  • gcc(Ubuntu)、*クラッシュなしの100k + !ここではハードウェアが限界だと思います。

(変数宣言のみでテスト済み)


5
実際、単項演算子のプロダクションは右再帰的です。つまり、shift-reduceパーサーはすべての*ノードをスタックにシフトしてから、リダクションを実行できます。
Kaz

28

制限はありません。ここで例を確認してください。

答えは、「ポインタのレベル」が何を意味するかによって異なります。「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レベルの間接指定(何かへのポインターへのポインター)が一般的です。それ以上だと、簡単に考えるのが少し難しくなります。代替案がさらに悪化しない限り、それを行わないでください。

「実行時にポインタインダイレクションのレベルをいくつ持つことができるか」という意味であれば、制限はありません。この点は、各ノードが次を指す循環リストの場合に特に重要です。プログラムはポインタを永久にたどることができます。


7
コンパイラは有限のメモリ量で情報を追跡する必要があるため、ほぼ確実に制限があります。(g++私のマシンでは98242で内部エラーが発生してアボートします。実際の制限はマシンと負荷に依存すると予想します。これが実際のコードの問題であるとは予想していません。)
James Kanze

2
うん、@ MatthieuM。:私は理論的に考えただけです:)答えを完成してくれたJamesに感謝します
Nandkumar Tekale

3
まあ、リンクされたリストは実際にはポインターへのポインターではなく、ポインターを含む構造体へのポインターです(それか、最終的に多くの不必要なキャストを行うことになります)
Random832

1
@ Random832:ナンドは、「実行時にポインタの間接化のレベルをいくつ持つことができるか」と言ったので、ポインタへのポインタ(* n)について話すという制限を明示的に削除しました。
LarsH

1
私はあなたの要点を理解していません: ' 制限はありません。ここで例を確認してください。'この例は、制限がないことを証明するものではありません。12スターの間接参照が可能であることを証明するだけです。どちらもcirc_list、OPの質問に関する例を何も証明しません。ポインターリストをトラバースできるという事実は、コンパイラーがnスターの間接参照をコンパイルできることを意味しません。
アルベルト

24

関数へのポインタを使用すると、さらにおもしろくなります。

#include <cstdio>

typedef void (*FuncType)();

static void Print() { std::printf("%s", "Hello, World!\n"); }

int main() {
  FuncType const ft = &Print;
  ft();
  (*ft)();
  (**ft)();
  /* ... */
}

ここに示されているように、これは次のようになります。

こんにちは世界!
こんにちは世界!
こんにちは世界!

また、ランタイムのオーバーヘッドは発生しないため、コンパイラがファイルを窒息させるまで、必要なだけスタックすることができます。


20

制限はありません。ポインタは、内容がアドレスであるメモリのチャンクです。
あなたが言ったように

int a = 10;
int *p = &a;

ポインターへのポインターは、別のポインターのアドレスを含む変数でもあります。

int **q = &p;

ここでqのアドレス保持ポインタへのポインタでp、すでにのアドレスを保持しているがa

ポインターへのポインターについては、特別なことは何もありません。
したがって、別のポインターのアドレスを保持しているponiterのチェーンに制限はありません。
すなわち。

 int **************************************************************************z;

許可されている。


17

すべてのC ++開発者は、(有名な)有名な3つ星プログラマについて聞いたことがあるはずです

本当にカモフラージュしなければならない魔法の「ポインターバリア」があるようです

C2からの引用:

3つ星プログラマ

Cプログラマのための評価システム。ポインタが間接的であるほど(つまり、変数の前の「*」が多いほど)、評判は高くなります。スターのないCプログラマーは、事実上すべての重要なプログラムがポインターの使用を必要とするため、事実上存在しません。ほとんどが1つ星のプログラマーです。昔(そうですね、私は若いので、少なくとも私には昔のように見えます)、3つ星のプログラマーによって行われたコードの一部を時々見つけ、畏敬の念を抱きました。一部の人々は、複数レベルの間接参照で、関数ポインターが含まれる3つ星のコードを見たとさえ主張しました。私にはUFOと同じくらいリアルに聞こえました。


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/…などは、4つ星のプログラマーによって作成されました。彼は私の友人でもあり、コードを十分に読めば、4つ星に値する理由を理解できます。
ジェフ

13

ここでは2つの可能な質問があることに注意してください。C型で達成できるポインター間接参照のレベル数と、単一の宣言子に詰め込むことができるポインター間接参照のレベル数です。

C規格では、前者に最大値を課すことができます(その最小値を指定します)。しかし、それは複数のtypedef宣言によって回避できます。

typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */

結局のところ、これは拒否される前にCプログラムをどのくらい大きく/複雑にできるかという考えに関連する実装の問題であり、これは非常にコンパイラー固有です。


4

任意の数の*で型を作成することは、テンプレートのメタプログラミングで発生する可能性があることを指摘しておきます。私は正確に何をしていたのか忘れてしまいましたが、再帰的な T *タイプを使用することで、ある種のメタ操作を行う新しい特殊タイプを作成できることが示唆されました。

テンプレートメタプログラミングは狂気へのゆっくりとした降下なので、数千レベルの間接参照を持つ型を生成するときに言い訳をする必要はありません。これは、たとえばペアノ整数を関数型言語としてのテンプレート展開にマッピングするための便利な方法です。


私はあなたの答えを完全に理解していませんが、それは私に探求する新しい領域を与えてくれます。:)
ankush981 2015年

3

2004年MISRA C規格のルール17.5は、2レベルを超えるポインターの間接参照を禁止しています。


15
これは、コンパイラーではなく、プログラマーへの推奨事項です。
Cole Johnson、

3
2レベルを超えるポインターの間接参照に関するルール17.5のドキュメントを読みました。そして、それは必ずしも2つ以上のレベルを禁止するわけではありません。それは、2つ以上のレベルが"non-compliant"彼らの基準にあるので、判決に従うべきであると述べています。彼らの判決で重要な単語または句は、"should"この声明の単語の使用です。Use of more than 2 levels of indirection can seriously impair the ability to understand the behavior of the code, and should therefore be avoided.これらは、言語標準によって設定された規則とは対照的に、この組織によって設定されたガイドラインです。
Francis Cugler 2017年

1

本当の限界のようなものはありませんが、限界は存在します。すべてのポインタは、通常ヒープではなくスタックに格納される変数です。スタックは通常小さい(リンク中にサイズを変更することが可能)。それでは、4MBのスタックがあるとしましょう。これはかなり通常のサイズです。そして、4バイトのサイズのポインターがあるとします(ポインターのサイズは、アーキテクチャー、ターゲット、およびコンパイラーの設定によって異なります)。

この場合4 MB / 4 b = 1024、可能な最大数は1048576ですが、他のものがスタックにあるという事実を無視してはなりません。

ただし、一部のコンパイラーはポインター・チェーンの最大数を持っている場合がありますが、制限はスタック・サイズです。したがって、無限大とのリンク中にスタックサイズを増やし、そのメモリを処理するOSを実行する無限メモリを備えたマシンを使用すると、無制限のポインタチェーンが得られます。

int *ptr = new int;ポインターを使用してヒープ内に配置する場合、これは通常の方法ではなく、スタックではなくヒープサイズが制限されます。

編集ただそれを理解してくださいinfinity / 2 = infinity。マシンのメモリが多い場合は、ポインタサイズが増加します。したがって、メモリが無限大でポインタのサイズが無限大の場合、それは悪いニュースです... :)


4
A)ポインターはヒープに保存できます(new int*)。B)an int*とan int**********は、少なくとも妥当なアーキテクチャでは同じサイズです。

@rightfold A)はい、ポインタはヒープに格納できます。しかし、次の前のポインターを指しているポインターを保持するコンテナーを作成する場合とはかなり異なります。B)もちろんint*int**********サイズは同じですが、サイズが異なるとは言いませんでした。
ST3 2013

2
次に、スタックサイズがリモートでどのように関連しているかはわかりません。

@rightfold私は、すべてのデータがヒープ内にあり、スタック上でそれがそのデータへの単なるポインタである場合のデータ分散の通常の方法について考えてきました。それは次のようになり、通常の方法が、私はスタックにポインタを置くことが可能であることに同意します。
ST3 2013

「もちろんint *とint **********は同じサイズです」-標準はそれを保証していません(私はそれが真実でないプラットフォームを知りません)。
Martin Bonnerがモニカ

0

ポインタを格納する場所によって異なります。それらがスタックにある場合、あなたはかなり低い制限を持ってます。ヒープに保存すると、制限ははるかに高くなります。

このプログラムを見てください:

#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

ところで。それは92KRAMを使用するので、どれだけ深く行くことができるか想像してみてください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.