我们可以拥有多少级指针?


443

*一个变量中允许多少个指针()?

让我们考虑以下示例。

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

同样,我们可以拥有

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

等等。

例如,

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

582
如果那对您来说成为一个现实问题,那么您所做的事情非常错误。
ThiefMaster 2012年

279
您可以继续添加指针级别,直到您的大脑爆炸或编译器崩溃为止-以最快的时间为准。
JeremyP

47
由于指向指针的指针又是指针,因此,理论上不应有任何限制。也许编译器将无法处理超出一些荒谬的上限,但是很好……
Christian Rau 2012年

73
使用最新的c ++,您应该使用类似std::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx 2012年

44
@josefx-这显示了C ++标准中的一个问题-无法提出指向幂的智能指针。我们必须立即要求扩展以支持(pow (std::shared_ptr, -0.3))<T> x;-0.3级间接。
2012年

Answers:


400

C标准规定了下限:

5.2.4.1翻译限制

276该实现应能够翻译和执行至少一个程序,该程序包含以下每个限制的至少一个实例:[...]

279 — 12个指针,数组和函数声明符(任意组合),用于修改声明中的算术,结构,联合或无效类型

上限是特定于实现的。


121
C ++标准“建议”实施至少支持256个。(可读性建议您不要超过2或3,甚至超过:2应该是例外。)
James Kanze 2012年

22
此限制大约是单个声明中的数目;它不会对通过倍数typedefs 可以实现多少间接性施加上限。
卡兹(Kaz)2012年

11
@Kaz-是的,是的。但是,由于规范(无双关语)指定了强制性下限,因此这是所有符合规范的编译器都必须支持的有效上限。当然,它可能低于供应商特定的上限。用不同的措辞(使之与OP的问题保持一致),它是规范所允许最大值 (其他任何东西都是特定于供应商的。)切线切线,程序员应该(至少在一般情况下)将其视为他们的上限(除非他们有合理的理由依赖于特定于供应商的上限)...我认为。
luis.espinal 2012年

5
在另一方面,我将开始削减自己,如果我不得不与代码的工作,早已屁股提领链(特别是当从优充塞所有的地方。)
luis.espinal

11
@铍:通常这些数字来自对预标准化软件的调查。在这种情况下,大概他们研究了常见的C程序和现有的C编译器,并发现至少有一个编译器遇到问题,如果将其限制为12,则超过12个和/或没有任何破坏程序

155

实际上,C程序通常使用无限指针间接寻址。一两个静态级别很常见。三重间接寻址很少见。但是无限是非常普遍的。

无限指针间接访问是借助结构实现的,当然,不是借助直接声明符实现的,这是不可能的。并且需要一个结构,以便您可以在此结构可以终止的不同级别上包含其他数据。

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

现在你可以了list->next->next->next->...->next。这实际上只是多个指针间接调用:*(*(..(*(*(*list).next).next).next...).next).next。并且.next当它是结构的第一个成员时,基本上是noop,因此我们可以将其想象为***..***ptr

对此实际上没有任何限制,因为可以使用循环而不是像这样的巨型表达式来遍历链接,此外,可以轻松地使结构成为圆形。

因此,换句话说,链表可能是添加另一个间接级别来解决问题的最终示例,因为您在每次推送操作中都会动态地执行此操作。:)


48
但是,这是一个完全不同的问题-包含指向另一个结构的指针的结构与指针-指针非常不同。int *****是与int ****不同的类型。
蓬松的

12
这不是“非常”不同。区别是蓬松的。它比语法更接近于语法。是指向指针对象的指针,还是指向包含指针的结构对象的指针?这是同样的事情。到达列表的第十个元素是寻址间接寻址的十个层次。(当然,表达无限结构的能力取决于该结构类型能否通过不完整结构类型指向自身,以便list->nextlist->next->next属于同一类型;否则我们将不得不构造一个无限类型。)
Kaz

34
当我使用“蓬松”一词时,我没有意识地注意到您的名字是蓬松的。下意识的影响?但是我确信我以前曾经用过这种词。
卡兹(Kaz)2012年

3
还要记住,在机器语言中,LOAD R1, [R1]只要R1在每一步都是有效的指针,就可以迭代类似的内容。除了“保存地址的单词”外,没有其他类型。是否存在声明的类型并不能确定间接性及其所具有的级别。
卡兹(Kaz)2012年

4
如果结构是圆形,则不是。如果R1持有指向自己的位置的地址,则LOAD R1, [R1]可以在无限循环中执行。
卡兹(Kaz)2012年

83

理论上:

您可以根据需要具有任意数量的间接级别。

几乎:

当然,不会消耗内存的任何事物都是不确定的,由于主机环境上的可用资源会受到限制。因此,实际上对实现可以支持的内容有最大的限制,并且实现应适当地记录下来。因此,在所有此类工件中,标准均未指定最大限制,但确实指定了下限。

这是参考:

C99标准5.2.4.1翻译限制:

— 12个指针,数组和函数声明符(任意组合),用于修改声明中的算术,结构,联合或无效类型。

这指定了每个实现必须支持的下限。请注意,该标准在脚注中进一步指出:

18)实施过程中应尽可能避免施加固定的翻译限制。


16
间接操作不会溢出任何堆栈!
Basile Starynkevitch 2012年

1
更正后,我有种种错误的感觉,即读取和回答q作为传递给函数的参数的限制。我不知道为什么?
Alok保存

2
@basile-我希望堆栈深度是解析器中的一个问题。许多正式的解析算法都将堆栈作为关键组件。大多数C ++编译器可能使用递归下降的变体,但即使如此,它也依赖于处理器堆栈(或者,就学上来说,依赖于好像有处理器堆栈的语言)。语法规则的更多嵌套意味着更深的堆栈。
2012年

8
间接操作不会溢出任何堆栈!->不!解析器堆栈可能溢出。 堆栈与指针间接关系如何?解析器堆栈!
Pavan Manjunath'4

如果*一行中有多个类被重载,并且每次重载都返回该行中其他类型的对象,则此类链接函数调用可能会有stackoverflow。
纳瓦兹

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编写了脚本,将出现*故障的脚本数量加倍,直到前一个脚本通过了;然后在该间隔内对第一个失败的脚本进行二进制搜索。整个测试用了不到一秒钟的时间来运行。)
James Kanze 2012年

63

听起来很有趣。

  • Visual Studio 2010(在Windows 7上)在出现此错误之前可以具有1011个级别:

    致命错误C1026:解析器堆栈溢出,程序太复杂

  • gcc(Ubuntu),超过100k,*无崩溃!我想硬件是这里的极限。

(仅通过变量声明进行测试)


5
实际上,一元运算符的乘积是右递归的,这意味着移位减少解析器会将所有*节点转移到堆栈上,然后才能进行归约。
卡兹(Kaz)2012年

28

没有限制,请在此处查看示例。

答案取决于您所说的“指针级别”。如果您的意思是“一个声明中可以有多少个间接级别?” 答案是“至少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 */

如果您的意思是“在程序难以阅读之前可以使用多少个级别的指针”,这是一个问题,但是有一个限制。通常有两个间接级别(一个指向某物的指针)。除此之外,更难考虑。除非替代方案会更糟,否则不要这样做。

如果您的意思是“在运行时可以具有多少级的指针间接访问”,则没有限制。这一点对于循环列表尤其重要,在循环列表中,每个节点都指向下一个节点。您的程序可以永远跟随指针。


7
几乎肯定有一个限制,因为编译器必须在有限的内存中跟踪信息。(g++我的机器在98242上因内部错误而中止。我希望实际的限制取决于机器和负载。我也不希望在实际代码中出现此问题。)
James Kanze 2012年

2
是的@MatthieuM。:我只是从理论上考虑:)谢谢James的完整回答
Nandkumar Tekale 2012年

3
好吧,链接列表实际上并不是指向指针的指针,它们是指向包含指针的结构的指针(要么指向那个指针,否则您最终会进行很多不必要的强制转换)
Random832

1
@ Random832:Nand说“如果您的意思是“在运行时可以有多少个级别的指针间接”,”因此他明确地消除了只谈论指针(* n)的限制。
LarsH 2012年

1
我不明白你的意思:' 没有限制,请在此处查看示例。'这个例子不能证明没有极限。它仅证明了12星间接定向是可能的。两者都没有证明circ_list关于OP问题的示例:您可以遍历指针列表的事实并不意味着编译器可以编译n-star间接寻址。
艾伯托(Alberto)

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

指向指针的指针没有什么特别的。
因此,拥有另一个指针地址的编织链没有限制。
即。

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

被允许。


17

每个C ++开发人员都应该听说过著名的三星级程序员

确实似乎有一些不可思议的魔术“指针障碍”

从C2引用:

三星级程序员

C程序员的评分系统。指针的间接性越强(即变量前的“ *”越多),您的声誉就越高。没有明星的C程序员实际上是不存在的,因为几乎所有不平凡的程序都需要使用指针。大多数是一星级程序员。在过去(嗯,我还很年轻,所以至少对我来说看起来像是过去),偶尔会发现由三星级程序员编写的一段代码,并敬畏地颤抖。甚至有人声称,他们在不止一级的间接访问中看到了包含函数指针的三星级代码。对我来说,听起来像不明飞行物一样真实。


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/…等由四星级程序员编写。他也是我的一个朋友,如果您足够阅读代码,您将理解为什么它值得四星级。
杰夫

13

请注意,这里存在两个可能的问题:在C类型中可以实现多少级指针间接寻址,以及可以在单个声明符中填充多少级指针间接寻址。

C标准允许对前者施加最大值(并为此给出最小值)。但这可以通过多个typedef声明来规避:

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

因此,最终,这是一个实现问题,涉及到一个C程序在被拒绝之前可以制作多大/复杂的想法,这是非常特定于编译器的。


4

我想指出的是,使用模板元编程可以生成带有任意数量*的类型。我忘记了我到底在做什么,但是有人建议我可以使用递归 T *类型生成新的不同类型,它们之间进行某种元操作。

模板元编程是一种缓慢的疯狂状态,因此在生成具有数千个间接级别的类型时,不必找借口。例如,这只是将peano整数映射为功能语言到模板扩展上的一种便捷方法。


我承认我不太了解您的答案,但这给了我一个新的探索领域。:)
ankush981

3

2004 MISRA C标准的规则17.5禁止超过两个级别的指针间接访问。


15
可以肯定的是,这是对程序员的建议,而不是对编译器的建议。
科尔·约翰逊

3
我使用规则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.这些是本组织制定的指南,而不是语言标准制定的规则。
弗朗西斯·库格勒

1

没有真正的限制之类的东西,但存在限制。所有指针都是通常存储在堆栈中而不是堆中的变量。堆栈通常很小(可以在某些链接中更改其大小)。因此,假设您有4MB堆栈,这是正常大小。可以说我们有一个4字节大小的指针(指针大小根据架构,目标和编译器设置的不同而不同)。

在这种情况下4 MB / 4 b = 1024,可能的最大数量为1048576,但是我们不应忽略其他一些东西在堆栈中的事实。

但是,某些编译器可能具有最大数量的指针链,但限制是堆栈大小。因此,如果在与infinity链接期间增加了堆栈大小,并且使计算机具有运行OS的infinity内存,该OS处理该内存,那么您将拥有无限的指针链。

如果使用int *ptr = new int;指针并将其放到堆中,则通常情况下限制不是堆大小,而是堆大小。

编辑才意识到infinity / 2 = infinity。如果机器有更多内存,则指针大小会增加。因此,如果内存是无限的,并且指针的大小是无限的,那么这是个坏消息... :)


4
A)指针可以存储在堆(new int*)上。B)至少在合理的架构上,an int*和an int**********具有相同的大小。

@rightfold A)是的,指针可以存储在堆中。但这将是完全不同的事情,例如创建容器来保存指向下一个上一个指针的指针。B)当然int*和的int**********尺寸相同,我没有说它们的尺寸不同。
ST3

2
然后,我看不到堆栈大小甚至与远程无关。

@rightfold我一直在考虑当所有数据都在堆中和在堆栈上时,通常的数据分配方式只是这些数据的指针。这是通常的方式,但是我同意可以将指针放在堆栈中。
ST3

“当然int *和int **********具有相同的大小”-该标准不保证(尽管我不知道没有平台是不正确的)。
马丁·邦纳

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

顺便说一句。它使用92K了RAM,因此,只需想象一下您可以走多深。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.