人们对C指针有什么困难?

The*_*aul 172 c pointers

从这里发布的问题数量可以清楚地看出,当人们围绕指针和指针算术时,人们会遇到一些非常有趣的问题.

我很想知道为什么.他们从来没有真正给我带来过重大问题(尽管我在新石器时代首次了解它们).为了更好地回答这些问题,我想知道人们发现什么困难.

所以,如果你正在努力使用指针,或者你最近突然"得到它",指针的哪些方面会导致你出现问题?

Jef*_*cht 144

当我第一次开始使用它们时,我遇到的最大问题是语法.

int* ip;
int * ip;
int *ip;
Run Code Online (Sandbox Code Playgroud)

都是一样的.

但:

int* ip1, ip2;  //second one isn't a pointer!
int *ip1, *ip2;
Run Code Online (Sandbox Code Playgroud)

为什么?因为声明的"指针"部分属于变量,而不是类型.

然后取消引用该东西使用一个非常相似的表示法:

*ip = 4;  //sets the value of the thing pointed to by ip to '4'
x = ip;   //hey, that's not '4'!
x = *ip;  //ahh... there's that '4'
Run Code Online (Sandbox Code Playgroud)

除非你真的需要一个指针...然后你使用一个&符号!

int *ip = &x;
Run Code Online (Sandbox Code Playgroud)

万岁一致!

然后,显然只是成为混蛋并证明它们有多聪明,许多库开发人员使用指向指针的指针,如果他们期望这些东西的数组,那么为什么不直接传递一个指针呢? .

void foo(****ipppArr);
Run Code Online (Sandbox Code Playgroud)

要调用它,我需要指向int的指针指针数组的地址:

foo(&(***ipppArr));
Run Code Online (Sandbox Code Playgroud)

在六个月内,当我必须维护这段代码时,我会花更多的时间来弄清楚这一切意味着什么,而不是从头开始重写.(是的,可能这个语法错了 - 因为我在C中做过任何事情已经有一段时间了.我有点想念它,但后来我有点像一个狂欢节)

  • 您对第一个的评论,>>*ip = 4; //将ip的值设置为'4'<<错误.它应该是>> //将ip所指向的东西的值设置为'4' (21认同)
  • 我真诚地无法相信对_syntax_的误解是最有选择性的答案.这是关于指针最简单的部分. (20认同)
  • @Jason除了大多数人认为困难之外,还有哪些客观的难度测量? (19认同)
  • 在任何语言中,将太多类型堆叠在一起是一个坏主意.你可能会发现写"foo(&(***ipppArr));" 在C中很奇怪,但是写了类似"std :: map <std :: pair <int,int>,std :: pair <std :: vector <int>,std :: tuple <int,double,std :: list <int >>>>"在C++中也非常复杂.这并不意味着C++中C或STL容器中的指针很复杂.它只是意味着您必须使用更好的类型定义,以使代码的读者能够理解它. (8认同)
  • 即使阅读这个答案,我也很想得到一张纸并画出画面.在C中,我总是画画. (4认同)
  • 这就是我喜欢的装配方式。您有一个 DWORD,可以将 is 视为 int、int*、指向任何结构或函数的指针,以及没有强制转换的所有内容。 (2认同)
  • C++使这更糟糕 - "&"被重载,因此它也可以在声明中使用(`int&ir(i);`声明一个引用).这意味着有两个符号(`*`和`&`),它们之间有6个含义(取决于它们在语法上出现的位置). (2认同)

jke*_*ian 87

我怀疑人们的答案有点太深了.实际上并不需要了解调度,实际CPU操作或汇编级内存管理.

当我在教学时,我发现学生的理解中存在以下漏洞是最常见的问题来源:

  1. 堆与堆栈存储.令人惊讶的是,即使在一般意义上,有多少人不理解这一点.
  2. 堆栈帧.只是局部变量的堆栈专用部分的一般概念,以及它是"堆栈"的原因...诸如存储返回位置,异常处理程序详细信息和以前的寄存器等详细信息可以安全地保留,直到有人试图构建一个编译器.
  3. "内存就是内存就是内存".转换只会更改运算符的版本或编译器为特定内存块提供多少空间.当人们谈论"什么(原始)变量X 真的是什么"时,你知道你正在处理这个问题.

我的大多数学生都能够理解一块内存的简化图,通常是当前范围内堆栈的局部变量部分.通常给各个位置提供明确的虚构地址有帮助.

总而言之,我想说如果你想理解指针,你必须理解变量,以及它们在现代架构中的实际含义.

  • 恕我直言,理解堆栈和堆与低级CPU细节一样不必要.堆栈和堆恰好是实现细节.ISO C规范没有单独提及"堆栈"一词,K&R也没有. (13认同)
  • @John Marchetti:更是如此......鉴于问题是"人们指针问题的根本问题是什么",我不认为人们会问与指针相关的问题会对"你不喜欢"留下深刻印象真的需要知道"作为答案.显然他们不同意.:) (9认同)
  • @sigjuice:你的反对​​意见错过了问答的重点.A)K&R C是不合时宜的B)ISO C不是唯一带指针的语言,我的第1点和第2点是针对非基于C的语言开发的C)95%的架构(不是语言)使用堆/ stack系统,相对于它解释异常是很常见的.D)问题的关键是"为什么人们不理解指针",而不是"我如何解释ISO C" (4认同)
  • @jkerian它可能已经过时了,但解释指针的三四页K&R不需要实现细节就可以了.由于各种原因,实现细节的知识是有用的,但是恕我直言,它不应该是理解语言的关键结构的先决条件. (3认同)
  • "通常会给各个地点提供明确的虚构地址." - > +1 (3认同)

Rob*_*vey 52

正确理解指针需要了解底层机器的架构.

今天许多程序员都不知道他们的机器是如何工作的,正如大多数知道如何驾驶汽车的人对发动机一无所知.

  • 我不同意; 你不需要理解底层架构来获取指针(无论如何它们都是抽象的). (30认同)
  • @dmckee:嗯,我错了吗?有多少Java程序员可以处理段错误? (18认同)
  • @Jason:在C中,指针本质上是一个*内存地址.*如果不了解机器架构,就不可能安全地使用它们.见http://en.wikipedia.org/wiki/Pointer_(computing)和http://boredzo.org/pointers/#definition (11认同)
  • @Robert:它意味着真正的补充.这是一个很难讨论的主题,不会伤害人们的感受.而且我担心我的评论会引发我认为你能避免的冲突.*Mea cupla.* (6认同)
  • 段错误与棒移位有关吗?* - Java程序员* (5认同)
  • @Jason:你是对的,你不需要理解架构来获取指针,但是如果你理解架构,那么它们很容易理解,如果你不了解它们会很痛苦.在我花了几年时间编写8502和6809汇编之后,我学会了C,而且我从未理解为什么人们在学习它们时会害怕指针.我以为它们就像索引寄存器一样...... (5认同)
  • C指针是一种抽象.如果您坚持使用标准的"已定义"位,那么在您不了解底层架构的情况下,您的代码将是安全的.实际上,在使用便携式标准C的情况下,许多事情都没有效率(甚至不可能),因此有关架构(和编译器!)的细节变得非常重要.但事实是指针是一种抽象,旨在隐藏底层平台的地址概念.地址是8,16,32还是64位?分段?标签?无所谓.便携式C将处理以上所有. (5认同)
  • @Jason:间接只告诉你故事的一部分.它无法帮助您理解数组和指针之间的关系,指针算术,如何编写生成代码的代码,函数指针,指向堆栈的指针与指向堆的指针之间的区别,或者真正的大部分最终都会咬人当你不理解它时.没有什么可以替代对至少一个抽象级别发生的事情的良好,可靠的理解,而不是你所使用的抽象级别. (5认同)
  • @Robert Harvey:有多少C++程序员可以编写易于理解,可维护,防漏的代码?提示 - 他们通常是同一个人.我不认为你的牛肉是Java,或者缺乏对底层机器的了解,但是编程人才的长尾:) (4认同)
  • 大声笑.在编程语言的所有开发之后,一群非常聪明的人仍在争夺C与Java.那真好笑. (3认同)
  • @Ori Pessach:我恭敬地不同意.底层架构只是妨碍了.如果你正确理解指针,你学到的东西同样适用于8502s和x86s.你在汇编中学到的东西是_indirection_,这是理解C指针的关键概念; 它与特定的架构无关. (2认同)

Dav*_*nco 42

在处理指针时,那些感到困惑的人在两个阵营中占有一席之地.我两个都在(我?).

array[]人群

这是直接不知道如何从指针表示法转换为数组表示法(或者甚至不知道它们甚至是相关的)的人群.以下是访问数组元素的四种方法:

  1. 数组符号(索引)与数组名称
  2. 带有指针名称的数组表示法(索引)
  3. 带指针名称的指针符号(*)
  4. 指针符号(*)与数组名称

 

int vals[5] = {10, 20, 30, 40, 50};
int *ptr;
ptr = vals;

array       element            pointer
notation    number     vals    notation

vals[0]     0          10      *(ptr + 0)
ptr[0]                         *(vals + 0)

vals[1]     1          20      *(ptr + 1)
ptr[1]                         *(vals + 1)

vals[2]     2          30      *(ptr + 2)
ptr[2]                         *(vals + 2)

vals[3]     3          40      *(ptr + 3)
ptr[3]                         *(vals + 3)

vals[4]     4          50      *(ptr + 4)
ptr[4]                         *(vals + 4)
Run Code Online (Sandbox Code Playgroud)

这里的想法是通过指针访问数组看起来非常简单和直接,但是通过这种方式可以完成大量非常复杂和聪明的事情.其中一些可能会让经验丰富的C/C++程序员感到困惑,更不用说没有经验的新手了.

reference to a pointerpointer to a pointer人群

是一篇伟大的文章,解释了差异,我将引用和窃取一些代码:)

作为一个小例子,如果您遇到类似这样的事情,很难确切地看到作者想要做什么:

//function prototype
void func(int*& rpInt); // I mean, seriously, int*& ??

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(pvar);
  ....
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

或者,在较小程度上,这样的事情:

//function prototype
void func(int** ppInt);

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(&pvar);
  ....
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以在一天结束时,我们真正用这些胡言乱语解决了什么?没有.

现在我们已经看到了ptr-to-ptr和ref-to-ptr的语法.一个优于另一个有什么优势吗?我很害怕,不.对于一些程序员来说,两者之一的使用仅仅是个人偏好.一些使用ref-to-ptr的人说语法是"更干净",而一些使用ptr-to-ptr的人,比如说ptr-to-ptr语法让那些阅读你正在做的人更清楚.

这种复杂性和看似(大胆看似)与引用的可互换性,这通常是指针的另一个警告和新手的错误,使得理解指针很难.同样重要的是理解,完成的缘故,该指针引用是C和C++非法的带你到混乱的原因lvalue- rvalue语义.

正如之前的回答所说,很多时候你会让这些热门的程序员认为他们使用起来很聪明******awesome_var->lol_im_so_clever(),我们大多数人有时可能会犯这样的暴行,但这只是不好的代码,而且肯定无法维护.

那么这个答案结果比我希望的要长......

  • 我想你可能在这里给出了C++问题的C++答案......至少第二部分. (5认同)
  • "指向引用的指针在C中是非法的" - 更像是"缺席":) (2认同)

Joh*_*ode 29

我个人指责参考资料和教学人员的质量; 在C(但大多数概念尤其是三分球)都只是简单的不好.我一直威胁要写自己的C书(题为"世界需要的最后一件事是C编程语言的另一本书"),但我没有时间或耐心去做.所以我在这里闲逛,并在人们的标准中随机引用.

还有的是当C最初的设计,这是事实假设你了解机器架构,以一个相当详细的水平,只是因为没有办法,以避免它在你的一天到一天的工作(记忆是如此紧张,处理器是如此慢你必须了解你写的是如何影响表现的).

  • 是.'int foo = 5; int*pfoo =&foo; 看看它有多有用?好的,继续......'在我编写自己的双链表库之前,我并没有真正使用指针. (3认同)
  • +1.我用来辅导CS100学生,他们的许多问题只是通过以可理解的方式查阅指针来解决. (2认同)

Ste*_*end 26

有一篇很棒的文章支持Joel Spolsky网站上的指针很难--JavaSchools的危机.

[免责声明 - 我本身并不是Java仇恨者.]

  • Spolsky并不是说JavaSchools是人们发现指针困难的原因.他说他们导致了计算机科学学位的指针文盲. (4认同)
  • @Jason - 这是真的但不否定论点. (2认同)
  • @Steve Townsend:Spolsky先生在争辩说Java学校正在培养一代不知道指针和递归的程序员,而不是因为Java学校的普遍性使得指针很难。正如您所说的“有一篇很棒的文章说明为什么这样做很难”并链接到所述文章一样,您似乎具有后一种解释。如果我错了,请原谅我。 (2认同)

Mik*_*vey 24

如果你没有基于"下面"的知识,那么大多数事情都难以理解.当我教CS时,当我开始让学生编写一个非常简单的"机器"时,它变得容易得多,这是一个带十进制操作码的模拟十进制计算机,其内存由十进制寄存器和十进制地址组成.他们会投入非常短的程序,例如,添加一系列数字来获得总数.然后他们会单步去看看发生了什么.他们可以按下"输入"键并观察它"快速"运行.

我敢肯定几乎所有人都想知道为什么这么基本有用.我们忘记了不知道如何编程的感觉.使用这样的玩具计算机就可以实现无法编程的概念,例如计算是一步一步的想法,使用少量基本原语来构建程序,以及记忆的概念变量作为存储数字的位置,其中变量的地址或名称与其包含的数字不同.您输入程序的时间与"运行"的时间之间存在差异.我把学习比作编程为跨越一系列"速度颠簸",例如非常简单的程序,然后是循环和子程序,然后是数组,然后是顺序I/O,然后是指针和数据结构.

最后,当进入C时,指针令人困惑,尽管K&R在解释它们方面做得非常好.我在C中学习它的方式是知道如何阅读它们 - 从右到左.就像我int *p在脑海中看到的那样,我说" p指向一个int".C被发明为汇编语言的一步,这就是我喜欢它 - 它接近于"基础".如果你没有这种基础,就像其他任何东西一样,指针更难理解.


J. *_*fer 17

在我阅读K&R中的描述之前,我没有得到指示.在那之前,指针没有意义.我读了很多东西,人们说"不要学习指针,它们会让人感到困惑,会伤到你的脑袋并给你动脉瘤",所以我很长时间地避开它,并创造了这种难以理解的不必要的气氛.

否则,大多数我认为的是,为什么你想要一个变量,你必须通过箍获得价值,如果你想分配东西,你必须做一些奇怪的事情来获得价值去进入他们.我想,变量的整个点是存储价值的东西,所以为什么有人想让它变得复杂在我之外. "所以使用指针你必须使用*运算符来获取它的值???这是什么样的傻瓜变量?" , 我想.毫无意义,没有双关语意.

它复杂的原因是因为我不明白指针是某个地址.如果你解释它是一个地址,它是包含一个地址的东西,并且你可以操纵该地址做有用的事情,我认为它可能清除混乱.

需要使用指针来访问/修改PC上的端口,使用指针算法来寻址不同的内存位置,以及查看修改其参数的更复杂的C代码的类,这使得我无法理解指针是毫无意义的.

  • 如果您使用有限的资源(RAM,ROM,CPU),例如在嵌入式应用程序中,指针很快就会更有意义. (4认同)

And*_*ell 12

这是一个让我暂停的指针/数组示例.假设您有两个数组:

uint8_t source[16] = { /* some initialization values here */ };
uint8_t destination[16];
Run Code Online (Sandbox Code Playgroud)

您的目标是使用memcpy()从源目标复制uint8_t内容.猜猜以下哪一项可以实现这一目标:

memcpy(destination, source, sizeof(source));
memcpy(&destination, source, sizeof(source));
memcpy(&destination[0], source, sizeof(source));
memcpy(destination, &source, sizeof(source));
memcpy(&destination, &source, sizeof(source));
memcpy(&destination[0], &source, sizeof(source));
memcpy(destination, &source[0], sizeof(source));
memcpy(&destination, &source[0], sizeof(source));
memcpy(&destination[0], &source[0], sizeof(source));
Run Code Online (Sandbox Code Playgroud)

答案(Spoiler Alert!)就是其中之一."destination","&destination"和"&destination [0]"都是相同的值."&destination" 与另外两种类型不同,但它仍然是相同的值."源"的排列也是如此.

另外,我个人更喜欢第一个版本.


nat*_*ose 7

我应该首先说C和C++是我学到的第一种编程语言.我从C开始,然后在学校里做了很多C++,然后又回到C语言,使其流利.

在学习C时,第一件让我困惑的事情很简单:

char ch;
char str[100];
scanf("%c %s", &ch, str);
Run Code Online (Sandbox Code Playgroud)

这种混乱主要是因为在将指针正确地引入我之前引入了对OUT参数的变量的引用.我记得我跳过在C for Dummies中编写前几个例子,因为它们太简单了,只是为了永远不会得到我写的第一个程序(很可能是因为这个).

让人感到困惑的是&ch实际意义以及为什么str不需要它.

在我熟悉之后,我记得我对动态分配感到困惑.我在某种程度上意识到,如果没有动态分配某种类型,指向数据并不是非常有用,所以我写了类似的东西:

char * x = NULL;
if (y) {
     char z[100];
     x = z;
}
Run Code Online (Sandbox Code Playgroud)

尝试动态分配一些空间.它没用.我不确定它会起作用,但我不知道它有什么用.

后来我才知道约mallocnew,但他们真的好像神奇记忆发电机给我.我对他们的工作方式一无所知.

一段时间后,我再次被教授递归(我以前自己学过它,但现在在课堂上),我问它是如何在引擎盖下工作的 - 存储了单独的变量.我的教授说"在堆栈上",很多事情对我来说很清楚.我以前听过这个术语,之前已经实现了软件堆栈.我早就听过其他人提到"堆栈",但却忘记了它.

大约在这个时候,我也意识到在C中使用多维数组会变得非常混乱.我知道它们是如何工作的,但是它们很容易纠缠在一起,我决定尽可能地尝试使用它们.我认为这里的问题主要是语法(特别是传递给函数或从函数返回它们).

因为我在接下来的一两年里为学校写C++,所以我在使用数据结构指针方面有很多经验.在这里,我遇到了一系列麻烦 - 混合指针.我会有多个级别的指针(类似的东西node ***ptr;),并总是绊倒自己.我会将指针取消引用错误的次数,并最终*通过反复试验来确定我需要多少指针.

在某些时候,我学会了程序的堆如何工作(有点好,但足够好,以至于它不再让我在晚上工作).我记得读过,如果你看一下指针之前的几个字节,malloc在某个系统上返回,你可以看到实际分配了多少数据.我意识到代码malloc可以从操作系统请求更多内存,而这个内存不是我的可执行文件的一部分.对工作方式有一个体面的工作理念malloc是非常有用的.

不久之后,我参加了一个集会课程,这并没有像大多数程序员所想的那样教我指针.尽管如此,它确实让我更多地考虑了我的代码可以被转换成什么程序集.我一直试图编写有效的代码,但现在我有了更好的想法.

我还花了几个课,我不得不写一些口齿不清.在编写lisp时,我并不像在C中那样关注效率.我很少知道如果编译可以将这些代码翻译成什么,但我确实知道它似乎使用了许多本地命名符号(变量)事情变得容易多了.在某些时候,我在一点点的lisp中编写了一些AVL树旋转代码,由于指针问题,我很难用C++编写.我意识到我对我认为过多的局部变量的厌恶阻碍了我在C++中编写它和其他几个程序的能力.

我还参加了一个编译器课程.在本课程中,我翻阅了进展材料并学习了静态 单一 赋值(SSA)和死变量,这并不重要,只不过它告诉我任何体面的编译器都能在处理变量方面做得不错.不再使用.我已经知道更多具有正确类型和好名字的变量(包括指针)可以帮助我把事情直接放在脑子里,但现在我也知道,出于效率原因而避免使用它们比我不那么微观优化的教授告诉他们更愚蠢我.

所以对我来说,了解程序的内存布局有很多帮助.考虑我的代码在符号和硬件上的含义对我有所帮助.使用具有正确类型的本地指针有很大帮助.我经常写代码如下:

int foo(struct frog * f, int x, int y) {
    struct leg * g = f->left_leg;
    struct toe * t = g->big_toe;
    process(t);
Run Code Online (Sandbox Code Playgroud)

因此,如果我搞砸了指针类型,编译器错误就会清楚问题是什么.如果我做了:

int foo(struct frog * f, int x, int y) {
    process(f->left_leg->big_toe);
Run Code Online (Sandbox Code Playgroud)

并且在那里得到任何指针类型错误,编译器错误将更难以弄清楚.我很想在我的挫折中诉诸试验和错误,并可能使事情变得更糟.


Spa*_*rky 6

回顾过去,有四件事真的帮助我最终理解了指针.在此之前,我可以使用它们,但我并不完全理解它们.也就是说,我知道如果我遵循表格,我会得到我想要的结果,但我并不完全理解表格的"原因".我意识到这并不是你所要求的,但我认为这是一个有用的推论.

  1. 编写一个带有指向整数的指针并修改整数的例程.这为我提供了构建指针如何工作的任何心理模型的必要形式.

  2. 一维动态内存分配.弄清楚1-D内存分配让我理解了指针的概念.

  3. 二维动态内存分配.找出2-D内存分配强化了这个概念,但也告诉我指针本身需要存储,必须考虑在内.

  4. 堆栈变量,全局变量和堆内存之间的差异.找出这些差异教会了指针指向/引用的内存类型.

这些项目中的每一项都需要想象在较低层次上发生的事情 - 建立一个心理模型,满足我能想到的每一个案例.这需要时间和精力,但这非常值得.我相信,为了理解指针,你必须建立关于它们如何工作以及如何实现它们的心智模型.

现在回到你原来的问题.根据之前的清单,有几个项目我最初难以掌握.

  1. 如何以及为什么使用指针.
  2. 它们有何不同,但与数组相似.
  3. 了解指针信息的存储位置.
  4. 了解指针指向的位置和位置.


Gar*_*owe 6

我用"指针时刻"处理C中的一些电话程序.我不得不使用仅理解经典C的协议分析器编写AXE10交换仿真器.一切都取决于知道指针.我试着在没有它们的情况下编写我的代码(嘿,我是"预指针"让我有些松懈)并完全失败了.

对我来说,理解它们的关键是&(地址)运算符.一旦我明白这&i意味着" 我的地址",那么理解*i意味着"我所指的地址的内容"稍后会出现.每当我编写或阅读我的代码时,我总是重复"&"的含义以及"*"的含义,最终我直观地使用它们.

令我感到羞耻的是,我被迫进入VB,然后是Java,所以我的指针知识不像以前那么尖锐,但我很高兴我是"后指针".不要让我使用一个需要我理解**p 的库.

  • 我有点超载i的用法.对于任意变量i,&i表示"i的地址",i就其自身表示"&i"的内容,而*i表示"将&i的内容视为地址,转到该地址,然后传回内容". (2认同)

sho*_*639 5

至少在我看来,指针的主要困难在于我没有从C开始.我从Java开始.整个指针的概念确实是外来的,直到我在大学里的几个课程,我被期望知道C.所以,然后我自学了C的基础知识以及如何在他们的基本意义上使用指针.即便如此,每当我发现自己正在阅读C代码时,我必须查找指针语法.

因此,在我非常有限的经历(1年真实世界+大学4年)中,指针让我感到困惑,因为除了课堂环境以外,我从来没有真正使用它.我现在可以同情学生用JAVA而不是C或C++开始CS.正如你所说,你学习了"新石器时代"时代的指针,并且从那以后可能一直在使用它.对于我们这些新人来说,分配内存和执行指针运算的概念实际上是外来的,因为所有这些语言都已经抽象出来了.

PS阅读完Spolsky文章后,他对"JavaSchools"的描述与我在康奈尔大学('05 -'09)所经历的完全不同.我采用了结构和函数式编程(sml),操作系统(C),算法(笔和纸),以及一系列未在java中讲授的其他类.然而,所有的介绍类和选修课都是用java完成的,因为当你试图做一些比使用指针实现散列表更高级别的东西时,不重新发明轮子是有价值的.

  • 笏?Java中的引用(或C#或Python,或者可能是其他几十种语言)只是没有算术的指针.理解指针意味着理解为什么`void foo(Clazz obj){obj = new Clazz(); ``是一个无操作的同时`void bar(Clazz obj){obj.quux = new Quux(); }`改变了论点...... (5认同)
  • 老实说,鉴于你仍然有指针困难,我不确定你在康奈尔大学的经历与乔尔的文章实质上相矛盾.很明显,你的大脑已经以Java思维方式连接起来以表达他的观点. (4认同)

eis*_*baw 5

这是一个非答案:使用cdecl(或c ++ decl)来解决它:

eisbaw@leno:~$ cdecl explain 'int (*(*foo)(const void *))[3]'
declare foo as pointer to function (pointer to const void) returning pointer to array 3 of int
Run Code Online (Sandbox Code Playgroud)