一个变量中允许多少个指针(*?
让我们考虑下面的例子。
1 2
| int a = 10;
int *p = &a; |
同样,我们可以
1 2
| int **q = &p;
int ***r = &q; |
等等。
例如,
- 如果这对你来说成为了一个真正的问题,那你就是在做一些非常错误的事情。
- 从技术上讲,可能没有限制(尽管这是特定于编译器的)。真正的限制是你(和你的同事)跟踪变量实际代表什么的心理能力。使代码可读通常比低级优化更重要。从这个角度来看,在生产代码中,在大多数情况下已经不鼓励三次间接(例如int***)。
- 你可以不断地增加指针的级别,直到你的大脑爆炸或编译器融化——无论哪种情况发生得最快。
- 因为指向指针的指针又是一个指针,好吧,只是一个指针,所以不应该有任何理论上的限制。也许编译器不能处理超过某个荒谬的上限,但是…
- 使用最新的C++,你应该使用类似于EDCOX1的1
- 也许是一个"装饰评论"失控,引发了这个问题:)
- 为什么不写一个简单的程序在编译器上测试这个呢?无论别人怎么回答,都可能与你的环境不完全匹配。
- @主持人:为什么?它的破坏性(或非建设性)是什么?
- @gowtham最糟糕的建议是,它可能在下一个版本中编译并失败,或者现在可能在编译器中失败。检查编译器文档,检查标准,最后检查当前的实现。
- 它对代码生成器有意义…
- JaseFX——这显示了C++标准中的一个问题——没有办法将智能指针提升到电源。我们必须立即要求延期,以支持-0.3级间接指令,如(pow (std::shared_ptr, -0.3)) x;。
- 这取决于系统的内存。如果超过该值,系统将挂起。否则,它是无限的。你可以用while(1)试试。
- @主持人:嗯,不。这是关于主题的。如果你们中有人想通过我的问题/答案,并大量投票,请感到自由!
- @彼得伍德,你提供的链接是黄金,只是纯金。
- @彼得伍德,我有维护一个三星级程序员代码库的"乐趣"。他还倾向于使用exit(0)处理所有错误情况(而不是其他任何情况,甚至是打印件)。
- 确定你的答案在这里?
- @你不确定吗?
- 似乎是强制CPP中使用意大利面代码的正确方法:)
- 从逻辑上讲,创建指向n级的指针没有任何意义。编译器没有任何问题,但如果您的程序使用它,它就有了
- @彼得伍德从来没有听说过这个词,但认识到我多年来遇到的许多程序员。感谢您分享这一精彩的链接。
- @彼德伍德曾经因为写三星级代码而被嘲笑过。虽然它实际上是一个字符串地址数组…在我看来,从一个指针到一个指针,再到一个指向char的指针,这和我被指控的非常不同。[叹息]我还是会捍卫这条准则。
- 我认识很多三星级的程序员。他们是量子化学家,以天真但普通的方式编程4D阵列。
- @史蒂文314:你是说,像这样?coliru.stacked-crooked.com/a/92a666764acbaff:d(堆叠弯曲)
- 请注意,有一个非常常见的指针,通常用三个级别编写:char**argv(或通常用char**argv[])编写)。例如,这正是您调用mpi_init(以及传递argv时可能更改argv的其他函数)的方式。
C标准规定了下限:
5.2.4.1 Translation limits
276 The implementation shall be able to translate and execute at least one program that contains at least one instance of every one of the following limits: [...]
279 — 12 pointer, array, and function declarators (in any combinations) modifying an
arithmetic, structure, union, or void type in a declaration
上限是特定于实现的。
- C++标准"建议"至少有256的实现支持。(可读性建议您不要超过2或3,即使这样:也应该有多个例外。)
- 这个限制是关于一个声明中有多少个;它不会对通过多个typedef可以实现多少间接寻址施加上限。
- @是的,那是真的。但是,由于规范(没有双关语)指定了强制的下限,所以它是所有规范兼容的编译器都需要支持的有效上限。当然,它可能低于特定于供应商的上限。以不同的方式重新表述(为了使其与OP的问题一致),这是规范所允许的最大值(任何其他内容都是特定于供应商的)。稍微偏离正切点,程序员应该(至少在一般情况下)将其视为上限(除非他们有合理的理由依赖特定于供应商的上限)。我想。
- 另一个注意事项是,如果我不得不使用具有长而无引用链的代码(特别是在到处乱扔的时候)。
- 好吧,但为什么正好是12?为什么不5或40呢?对此有什么解释吗?
- @铍:通常这些数字来自于对预标准化软件的调查。在这种情况下,他们大概研究了常见的C程序和现有的C编译器,发现至少有一个编译器在超过12个的情况下会出现问题,并且/或者如果将其限制在12个,则不会出现程序中断。
- 我不明白为什么一个答案会因为作者的名声而得到更多的赞成票。这样的答案实际上是好的,但是名声不好。这个答案没有那么有建设性,但是它的赞成票是巨大的。以上萨钦-米赫塔的回答是好的,但上票率很低。
- @当我发表我的答案时,我的代表大约是1500人。当时所有现有的答案都是错误的。因此,它得到了许多赞成票。稍后,其他人编辑了他们的答案和/或在我发布我的答案后进行了改进。您可以从答案的编辑历史记录和时间戳中看到这一点。例如,请参见编辑历史记录中的alok save的初始答案,这是完全错误的。
- @P.P.不过,Sachin Mehra的回答是建设性的,我看到了它的编辑历史,没有大的变化,你只是发布了一些标准,没有用你的话解释,它有超过300个赞成票。也许是因为OP没有问为什么,只是问什么是上限,最好的方法是显示标准,但如果你能解释的话会更好。n推理。像理论上和实践上一样,DownMihai用户对其进行了检查,并展示了什么是限制,什么是具体的实现,所有这些问题在一个答案中都使其值得所有的上票,而不仅仅是参考标准。
- @如果没有达到你的标准,你可以发布一个更好的答案。如果你关心的是上票的"应得性",你可以把它标记给一个调停者,并解释正确的上票数量,看看他们是否能帮助你。
实际上,C程序通常使用无限指针间接寻址。一个或两个静态级别是常见的。三人间接是罕见的。但是无限是很常见的。
当然,无限指针间接是在结构的帮助下实现的,而不是直接声明器,这是不可能的。并且需要一个结构,这样您就可以在该结构中的不同级别包含其他数据,从而终止该结构。
1
| struct list { struct list *next; ... }; |
现在您可以使用list->next->next->next->...->next。这实际上只是多指针间接寻址:*(*(..(*(*(*list).next).next).next...).next).next。当它是结构的第一个成员时,.next基本上是一个noop,所以我们可以把它想象成***..***ptr。
这确实没有限制,因为链接可以通过循环而不是像这样的巨型表达式来遍历,而且结构可以很容易地变成圆形。
因此,换句话说,链接列表可能是添加另一个间接级别以解决问题的最终示例,因为您在对每个推送操作进行动态操作。:)
- 不过,这是一个完全不同的问题——包含指向另一个结构的指针的结构与指针指针的结构非常不同。int****是与int****不同的类型。
- 这并不是"非常"不同。差别很大。它比语义更接近语法。指向指针对象的指针,还是指向包含指针的结构对象的指针?这是同样的事情。进入列表的第十个元素是解决间接寻址的十个级别。(当然,表示无限结构的能力取决于结构类型能够通过不完整的结构类型指向自身,以便list->next和list->next->next是同一类型;否则我们将不得不构造无限类型。)
- 当我使用"fluffy"这个词时,我没有意识到你的名字是fluffy。亚意识影响?但我确信我以前用过这个词。
- 六羟甲基三聚氰胺六甲醚!我不确定这真的是一个无限指针间接指向。从语义上讲,您只处理一种类型(struct list *类型),并且一次只引用一个指针,每个指针都由一个"几乎没有op"填充。(实际上已经指出了,但是嘿。)
- 在C语言中,不管什么,每次都会取消引用一个指针。在加载前一个指针之前,不能取消对下一个指针的引用。*******x是一次一个,和x->next->next->next->next完全一样。
- 还要记住,在机器语言中,只要R1在每个步骤都是一个有效的指针,就可以迭代类似于LOAD R1, [R1]的东西。除了"保存地址的单词"之外,没有涉及其他类型。是否存在已声明的类型并不决定间接寻址及其具有多少级别。
- + 1。但是在严格意义上——对于无限数量的间接寻址——我们必须有无限的地址空间,事实并非如此。
- 如果结构是圆形的,则不会。如果R1拥有指向自身的位置的地址,那么LOAD R1, [R1]可以无限循环执行。
- 这个答案没有回答有关声明的问题。
- @布莱恩,这当然是一个声明。结构声明是自引用的("我包含指向我自己的指针"),这是C通过不完整的结构类型机制刻意允许的。这将创建无限级别的指针间接寻址。
- @kaz的要点是,一个长的->next元素链的遍历由程序员控制;要处理*****....*****int,编译器必须在内部表示指针的深度(以便能够检测到不正确的解引用)。存储这种表示的某些方法可能只受内存限制(由编译器使用),但某些方法可能有硬限制。
- @tripehound P->memb和*P都是语法。该语法嵌套的深度由程序员控制。编译器必须表示P->memb->memb->memb->memb...,它必须表示***..P。
- 有一个区别,主要是在编译期间。对于恰好指向另一个P的P->next,编译器只需要知道它是指向父结构的指针。在编译时,它不关心在运行时创建的链接元素字符串有多长。对于*****int,编译器在编译时必须表示每个级别的类型…也就是说,*****int是指向****int的指针,指向***int。到一个*int,最后指向一个int。对于某些编译器,在某些情况下,这样做可能会耗尽空间。
- @tripehound可以避免使用类似于DEREFI(DEREFV(DEREFV...(DEREFV(P)) ...))的东西来构造深度静态类型,其中P是void *指针,DEREFV是#define DEREFV(P) (*((void **) (P))),DEREFI类似,但有一个强制转换为int *。即指向void *的任意长的void *指针链,最后指向int。
理论上:
你可以随心所欲地进行多层次的间接审判。
实际上:
当然,没有任何消耗内存的东西是不确定的,因为主机环境上的可用资源会有限制。因此,实际上,对一个实施所能支持的内容有一个最大限度的限制,并且该实施应适当地记录下来。因此,在所有这些工件中,标准没有指定最大限制,但它指定了下限。
参考资料如下:
C99标准5.2.4.1翻译限制:
— 12 pointer, array, and function declarators (in any combinations) modifying an
arithmetic, structure, union, or void type in a declaration.
这指定了每个实现必须支持的下限。请注意,在脚注中,标准进一步说明:
18) Implementations should avoid imposing fixed translation limits whenever possible.
- 间接寻址不会溢出任何堆栈!
- 堆栈如何与指针间接相关?
- 更正后,我有一种错误的感觉,即读取和回答Q作为传递给函数的参数限制。我不知道为什么?!
- @巴兹尔-我希望在解析器中堆栈深度是一个问题。许多正式的解析算法都有一个堆栈作为关键组件。大多数C++编译器可能使用递归下降的变体,但即使依赖于处理器堆栈(或者,迂回地,在语言上表现为好像存在一个处理器栈)。更多的语法规则嵌套意味着更深的堆栈。
- 间接寻址不会溢出任何堆栈!-->不!分析器堆栈可能溢出。堆栈如何与指针间接相关?解析器堆栈!
- 如果对一行中的多个类重载*,并且每个重载都返回该行中其他类型的对象,那么对于此类链接函数调用,可能存在stackoverflow。
- @当编译器解析由多个*组成的表达式时,p&233;tert&246;r&246;k可能是堆栈问题。
- 从实践的角度来看,翻译限制充其量只是建议,因为该标准没有说明哪些限制组合必须得到支持。如果代码试图在一个函数中包含最大允许的参数和自动对象数,并且每个参数和自动对象都占用了32767字节,那么许多现实世界的实现就会死掉。另一方面,标准中的任何内容都不会禁止一致但无用的实现说,任何函数都不能包含多个自动对象,并且具有任何大于一个字节的自动对象。
- 该标准的作者承认了一致但无用的实现的可能性,但期望(1)人们真诚地努力生成一个可用的实现,不会仅仅因为标准允许他们这样做而做愚蠢的事情,(2)不值得担心PEO可能生成哪种类型的编译器。不努力的人。
正如人们所说,理论上没有限制。不过,出于兴趣,我用G++4.1.2运行了这个程序,它的大小可以达到20000。编译非常慢,所以我没有尝试更高的编译速度。所以我想G++也没有任何限制。(尝试设置size = 10并查看ptr.cpp,如果它不是立即明显的。)
g++ create.cpp -o create ; ./create > ptr.cpp ; g++ ptr.cpp -o ptr ; ./ptr
肌酸蛋白磷酸酶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream>
int main()
{
const int size = 200;
std::cout <<"#include <iostream>
";
std::cout <<"int main()
{
";
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 <<";
";
}
std::cout <<" std::cout <<";
for (int i = 1; i < size; ++i) std::cout <<"*";
std::cout <<"i" << size-1 <<" << "\
";
";
std::cout <<" return 0;
}
";
return 0;
} |
- 当我尝试的时候,我不能得到超过98242。(我用python编写了这个脚本,将EDOCX1的数目加倍(0),直到我得到一个失败的脚本,以及前面一个通过的脚本;然后在这个时间间隔内对第一个失败的脚本进行了二进制搜索。整个测试只花了不到一秒钟的时间。)
听起来很有趣。
(仅以一项可变宣言测试)
- 实际上,一元运算符的结果是正确的递归的,这意味着一个SHIFT reduce解析器将把所有*节点转移到堆栈上,然后才能进行缩减。
没有限制,请检查这里的示例。
答案取决于您所说的"指针级别"。如果您所说的"一个声明中可以有多少个间接级别?"答案是"至少12个"。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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 */ |
如果你的意思是"在程序变得难以读取之前,你可以使用多少级指针",那是一个品味问题,但是有一个限制。有两个间接的层次(指向某物的指针)是常见的。任何超过这一点的事情都会变得更难思考;除非选择的方案更糟,否则不要这样做。
如果您的意思是"运行时可以有多少级指针间接寻址",则没有限制。这一点对于循环列表尤其重要,在循环列表中,每个节点都指向下一个节点。你的程序可以永远跟随指针。
- 几乎可以肯定有一个限制,因为编译器必须在有限的内存中跟踪信息。(g++在我的机器上以内部错误98242中止。我预计实际的限制将取决于机器和负载。我也不希望这在真正的代码中是一个问题。)
- 是的@matthieum.:我刚从理论上考虑过:)感谢詹姆斯完成了答案。
- 好吧,链表不是指向指针的指针,而是指向包含指针的结构的指针(或者你最终做了很多不必要的转换)
- @兰登832:是的!但你不认为它结构良好吗?
- @random832:nand说,"如果你的意思是"你在运行时可以有多少级的指针间接寻址",那么他明确地取消了仅仅谈论指向指针的指针的限制(*n)。
- 我不明白你的意思:"没有限制,请看这里的例子。"这个例子不能证明没有限制。它只证明了12星的间接寻址是可能的。对于op的问题,circ_list示例也不能证明任何东西:您可以遍历指针列表这一事实并不意味着编译器可以编译N星间接寻址。
- 我的意思是,没有限制,更多信息请查看这个例子。考虑一下循环列表示例。
使用指向函数的指针实际上更有趣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <cstdio>
typedef void (*FuncType)();
static void Print() { std::printf("%s","Hello, World!
"); }
int main() {
FuncType const ft = &Print;
ft();
(*ft)();
(**ft)();
/* ... */
} |
如图所示,这给出了:
Hello, World!
Hello, World!
Hello, World!
而且它不涉及任何运行时开销,所以您可以根据需要将它们堆叠起来…直到编译器在文件上阻塞。
没有界限。一个指针是一个记忆中的一个错误,它的内容是一个地址。如你所说
1 2
| int a = 10;
int *p = &a; |
指针是另一指针的地址的变量。
ZZU1
这里是指针指针控制器的地址是p,该地址已准备好控制器的地址是a。
指针对指针没有特别的意义。因此,在小马链上没有任何限制,而小马链上有另一个指针的地址。他
1
| int **************************************************************************z; |
这是允许的。
注意,这里有两个可能的问题:在C类型中,我们可以实现多少级的指针间接寻址,以及我们可以将多少级的指针间接寻址填充到一个声明符中。
C标准允许对前者施加最大值(并给出最小值)。但这可以通过多个typedef声明来规避:
1 2 3
| typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */ |
因此,最终,这是一个与C程序在被拒绝之前有多大/多复杂的想法相关的实现问题,这是非常特定于编译器的。
每个C++++++开发者都应该听得到三个著名的星程序员
看上去有点神奇的"指针屏障"需要伪装
BLCK1/
- github.com BLOB / / / / psi4 psi4public硕士/ SRC /图书馆/ libdpd / & hellip;什么样的书面由一个4明星程序员。所以他是我的一个朋友,如果你没有阅读代码,你想知道原因为什么它值得5星。
我想说的是,制作一个任意数*的人,是一个可以用模板编程的人。我忘了我做的是什么,但它建议我可以生产一些新的不同类型的金属,用T*类型。
温度偏编程是一个缓慢的下降到疯狂,所以当产生一种具有几种间接离子水平的类型时,不需要道歉。这只是一种绘制Peano Integers地图的方法,比如说,以功能语言的形式缓慢扩张。
- 我承认,我不完全明白你的答案,但它给我一个新的探索区域。:)
Rule 17.5 of the 2004 MISRA C Standard Prohibits more than 2 levels of pointer direction.
- 很肯定这是程序员的建议,而不是编译器的建议。
- 我阅读了规则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.:这些是本组织制定的指南,而不是语言标准制定的规则。
没有什么比实际限制更重要的了所有指针都是变量,通常存储在堆栈不加热。堆栈通常很小(在某些链接中,堆栈的尺寸可能改变)。所以说你有4MB堆栈,什么是正常尺寸。并让我们说,我们有4个字节大小的指针(指针大小与建筑、目标和编译设置不同)。
在本案中,可能的最大数目是10548576,但我们不应忽视其他一些工作人员的事实。
然而,有些编译器可能有最大的指针链,但极限是大小。所以,如果你在无限连接中增加堆栈的尺寸,并拥有无限记忆的机器,它运行的是无限记忆的手柄,所以你将拥有无限的指针链。
如果你使用int *ptr = new int;,并将指针放到热中,这不是很常见的限制尺寸,而不是堆栈。
版权声明:转载时请以超链接形式标明文章原始出处。如果机器有更多的记忆,指针的尺寸就会增加。如果记忆是无限的,指针的大小是无限的,那么这是坏消息。注:
- a)指针可以存储在堆(new int*)。b)int*和int**********有相同的大小,至少在合理的架构。
- "rightfold a)可以是指针存储在堆。但它会很不同的东西,这样的容器持有指针指向下一个是什么以前的指针。b)int*学院和int**********有相同的尺寸,我不说,他们有不同的。
- 我不了解remotely堆栈大小是不相关的。
- 只是实现了一个new *int,对不起,你是正确的
- "我一直在思考rightfold正常数据分布方式,当所有的数据是在堆和栈是在刚刚在那一分的数据。它会采取的方式,它是可能的,但我同意把分堆栈。
- "当然在int int * * * * * * * * * * * * * * * * *有同样大小的标准"不担保,(虽然我知道没有平台,它不是真的)。
这取决于你的店铺指针的位置。如果他们在堆栈里,你就离开了低极限。如果你把它放在头顶上,你的限制是非常高的。
Look at this program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #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;
} |
它创造了1米的指针,并展示了什么是方便地知道链条向第一个变量EDOCX1〕〔7〕
所以想象一下你能走多远。