我们可以有多少级指针?

474

在一个单一变量中可以有多少个指针 (*)?

让我们考虑以下示例。

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

同样地,我们也可以有

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

等等。

例如,

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

618
如果那成为你真正面临的问题,那么你正在做某件非常错误的事情。 - ThiefMaster
297
你可以不断添加指针级别,直到你的大脑爆炸或编译器崩溃——以先发生的为准。 - JeremyP
54
因为指向指针的指针本质上只是一个指针,所以理论上没有任何限制。也许编译器在超过某个非常高的限制后无法处理它,但是... - Christian Rau
79
使用最新的C++,应该使用类似于 std::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>> 这样的东西。 - josefx
52
@josefx - 这显示了C++标准的一个问题 - 没有办法将智能指针提升到幂。我们必须立即要求扩展以支持例如 (pow (std::shared_ptr, -0.3))<T> x; 的-0.3间接层级。 - user180247
显示剩余13条评论
14个回答

418

C标准规定下限:

###5.2.4.1 翻译限制

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

279 — 在任何组合中修改算术、结构、联合或void类型的12个指针、数组和函数声明符的声明

上限由具体实现决定。


124
C++标准“建议”实现至少支持256个。但为了易读性,不要超过2或3个,而且即使超过也应该是例外情况。 - James Kanze
23
这个限制关于单个声明中有多少内容;它并不会对你通过多个 typedef 实现的间接性达到的上限施加限制。 - Kaz
13
@Kaz - 是的,那是真的。但由于规范(没有双关语)指定了一个强制下限,这就是所有符合规范的编译器必须支持的有效上限。当然,它可能比供应商特定的上限要低。用不同的措辞表达(以与OP的问题对齐),它是由规范允许的最大值(其他任何值都是供应商特定的)。有点离题,程序员们应该(至少在一般情况下)将其视为他们的上限(除非他们有一个有效的理由依赖于供应商特定的上限)...我觉得。 - luis.espinal
5
另外提一句,如果我必须处理一个到处都是冗长的解引用链的代码时(特别是如果这种情况随处可见),我会开始自残。 - luis.espinal
12
通常这些数字是通过对未标准化的软件进行调查得出的。在这种情况下,他们可能会查看常见的C程序和现有的C编译器,并找到至少一个编译器,如果超过12个变量,则会遇到问题,或者没有任何程序会因将其限制为12而崩溃。 - user79758
显示剩余5条评论

163

实际上,C程序通常利用无限指针间接引用。一到两个静态级别很常见,三重间接引用则很少见。但是无限引用却非常普遍。

当然,无限指针间接引用是通过结构体实现的,而不是直接声明器,因为直接声明器是不可能的。需要结构体,以便您可以在这些可以终止的不同级别上包含其他数据。

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

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

这种方式并没有什么限制,因为链接可以通过循环而不是像这样的巨大表达式来遍历,而且结构可以很容易地被变成圆形。

因此,换句话说,链表可能是解决问题添加另一层间接性的终极示例,因为您在每次推操作时都要动态进行。


51
不过,这是一个完全不同的问题——包含指向另一个结构体的指针的结构体与指针指针非常不同。int***** 是一种不同的类型,与 int**** 不同。 - fluffy
15
并没有“非常”不同,区别在于蓬松度。它更接近语法而非语义学。是指向指针对象还是指向包含指针的结构体对象?这是同一种类型的事情。访问列表的第十个元素需要十个级别的寻址间接性(indirection)。(当然,能否表示无限结构的能力取决于结构类型能否通过不完整的结构类型指向自身,以便 list->nextlist->next->next 是相同类型;否则我们就必须构建一个无限的类型。) - Kaz
39
当我使用“fluffy”这个词时,并没有有意识地注意到你的名字叫做“fluffy”。是潜意识的影响吗?但是我肯定之前也用过这个词来表达同样的意思。 - Kaz
4
请记住,在机器语言中,只要R1在每一步都是一个有效的指针,你就可以像下面这样迭代:LOAD R1, [R1]。除了“保存地址的字”,没有涉及任何类型。声明的类型与否不决定间接引用和它有多少级。 - Kaz
4
如果结构是循环的,那么情况就不同了。如果'R1'保存了一个指向自身的位置的地址,那么执行LOAD R1, [R1]就会进入无限循环。 - Kaz
显示剩余9条评论

84

理论上:

您可以有任意多层间接引用。

实际上:

当然,任何消耗内存的东西都不能无限制地进行,由于主机环境中可用的资源限制,实际上会有限制。因此,实现对于支持的最大限制应有适当的文档说明。所以在所有这些工件中,标准没有规定最大限制,但它确实指定了下限。

以下是参考信息:

C99标准5.2.4.1翻译限制:

- 在声明中修饰算术、结构、联合或void类型的12个指针、数组和函数声明符(以任何组合形式)。

这明确了每个实现必须支持的下限。请注意,在注释中,标准进一步说明:

18) 尽可能避免强制实施固定的翻译限制。


17
间接引用不会使堆栈溢出! - Basile Starynkevitch
1
更正一下,我读到并回答了这个问题时有一种奇怪的感觉,好像是函数传递参数的限制。我不知道为什么?! - Alok Save
2
@basile - 我认为堆栈深度在解析器中可能是一个问题。许多正式的解析算法都有堆栈作为关键组成部分。大多数C++编译器可能使用递归下降的变体,但即使如此,它也依赖于处理器堆栈(或者严谨地说,依赖于语言表现得好像有一个处理器堆栈)。语法规则的更多嵌套意味着更深的堆栈。 - user180247
9
间接引用不会溢出任何堆栈!--> 不!解析器堆栈可能会溢出。堆栈与指针间接引用有什么关系?--> 解析器堆栈! - Pavan Manjunath
如果在一系列类中重载了 * 运算符,并且每个重载都返回该系列中其他类型的对象,则此类链接函数调用可能会导致堆栈溢出。 - Nawaz
显示剩余3条评论

80

正如其他人所说,“理论上”是没有限制的。然而,出于兴趣,我使用 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;
}

77
我尝试了一下,但最大数只有98242。我使用 Python 编写了脚本,将 * 的数量翻倍直到出错,然后倒退一个数量级的 * 直到可以运行为止。接着,在这个区间内进行二分查找,找到第一个失败的点。整个测试不到一秒钟就完成了。 - James Kanze

64

听起来很有趣。

  • 在Windows 7上的Visual Studio 2010中,您可以拥有1011个级别,然后会出现以下错误:

    致命错误 C1026: 分析器堆栈溢出,程序过于复杂

  • 在Ubuntu上的gcc编译器中,100k+的*没有崩溃!我猜硬件是限制。

(测试只用了一个变量声明)


5
事实上,一元操作符的产生式是右递归的,这意味着移进规约解析器在能够进行规约之前会将所有的 * 节点都移入堆栈。 - Kaz

31

没有限制,可以参考指针::C面试题和答案的例子。

答案取决于你所说的“指针级别”的含义。如果是指“在单个声明中可以有多少级间接性?” 那么答案是“至少为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
2
是的,@MatthieuM。我只是在理论上考虑了一下 :) 感谢James完成了答案。 - Nandkumar Tekale
3
好的,链表其实不是指向指针的指针,它们是指向一个包含指针的结构体的指针(否则你会做很多不必要的类型转换)。 - Random832
1
@Random832:Nand说:“如果你的意思是'在运行时可以有多少级指针间接引用',”那么他明确地消除了仅谈论指向指针(*n)的限制。 - LarsH
1
我不理解你的观点:“_没有限制,在这里检查示例_”。该示例不能证明没有限制,它只证明了可以进行12星间接引用。对于OP的问题,“circ_list”示例也没有任何证明:指针列表可以被遍历并不意味着编译器能够编译n星间接引用。 - Alberto
显示剩余2条评论

26

指向函数的指针会使它变得更有趣。

#include <cstdio>

typedef void (*FuncType)();

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

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

此处所示,这将产生:

Hello, World!
Hello, World!
Hello, World!

它不涉及任何运行时开销,因此您可以堆叠它们,直到编译器无法处理该文件为止。


22

没有限制。指针是一块内存,其内容是一个地址。
正如你所说的

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

指向指针的指针也是一个变量,它包含另一个指针的地址。

int **q = &p;

这里的q是指向指针p的指针,而p已经持有a的地址。

指向指针的指针并没有什么特别之处。因此,持有另一个指针地址的指针链条没有限制。
即。

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

允许。


19

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

似乎有一个神奇的“指针屏障”必须掩盖。

C2网站上的引用:

三星程序员

C程序员的评级系统。您的指针间接性越高(即在变量前有更多“*”),您的声望就越高。没有星号的C程序员几乎不存在,因为几乎所有非平凡的程序都需要使用指针。大多数人都是一星程序员。在旧时代(好吧,我还年轻,至少对我来说这看起来像是旧时代),偶尔会发现由三星程序员编写的代码,并因敬畏而颤抖。

有些人甚至声称,他们见过涉及函数指针的三星代码,涉及多层间接寻址。对我来说听起来像UFO一样真实。


13

请注意这里有两个可能的问题:在C类型中,我们可以实现多少级指针间接引用,以及我们可以将多少级指针间接引用塞入单个声明符中。

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

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

所以,最终这是一个与C程序的大小/复杂度有关的实现问题,这取决于编译器的具体规定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接