为什么用这种方式设计数组,指针和函数的C语法?


16

看过(问!)之后,有很多类似的问题

int (*f)(int (*a)[5])在C 中是什么意思?

甚至看到他们制作了一个程序来帮助人们理解C语法,我不禁想知道:

为什么用这种方式设计C的语法?

例如,如果我正在设计指针,我会将“指向10个元素的指针数组的指针”转换为

int*[10]* p;

不是

int* (*p)[10];

我觉得大多数人都同意,这要简单得多。

所以我想知道,为什么这种不直观的语法?语法是否解决了我未意识到的特定问题(也许有歧义?)?


2
您知道对此没有真正的答案。对?您将得到的只是猜测。
2011年

7
@VJo-可能会有一个“真实”(即客观)的答案-语言作者和标准委员会都已明确证明(或至少解释了)许多此类决定。
2011年

我认为您提出的语法不一定比C语法更“直观”。C就是它;一旦学会了它,您将再也不会遇到这些问题。如果您还没有学到……好吧,也许这是真正的问题。
迦勒

1
@Caleb:有趣的是你这么容易得出结论,因为我学到了,但我仍然有这个问题……
user541686

1
cdecl命令对于解码复杂的C声明非常方便。cdecl.org上还有一个Web界面。
基思·汤普森

Answers:


16

我对它的历史的理解是,它基于两个要点...

首先,语言作者倾向于使语法以变量为中心,而不是以类型为中心。也就是说,他们在声明中需要一个程序员的外观,并认为“如果我写的表情*func(arg),那将导致int;如果我写*arg[N]我有一个浮动”,而不是“ func必须是一个指向函数服用并返回那个 ”。

维基百科上C条目声称:

Ritchie的想法是在类似于标识符使用的上下文中声明标识符:“声明反映了使用”。

...引用K&R2的p122,a,我不必亲自为您找到扩展报价。

其次,在处理任意级别的间接访问时,要想出一个一致的声明语法实际上非常非常困难。您的示例可能很好地表达了您当即想到的类型,但是它是否可以扩展为使用指向这些类型的数组的指针并返回一些其他麻烦的函数呢?(也许是,但是您检查了吗?可以证明吗?)。

记住,C的成功部分是由于编译器是为许多不同的平台编写的,因此为了使编译器更易于编写,最好忽略某种程度的可读性。

话虽如此,我不是语言语法或编译器编写方面的专家。但是我知道很多,所以还有很多要知道的;)


2
“使编译器更易于编写” ...除了C语言因难以解析而臭名昭著(仅在C ++之上)。
Jan Hudec

1
@JanHudec-好吧。那不是水密的声明。但是,尽管C不可能解析为上下文无关的语法,但一旦有人提出了解析它的方法,这将不再是困难的一步。事实是,由于人们能够轻松地对编译器进行调试,因此它在早期多产的,因此K&R必须取得了一定的平衡。(在理查德·加布里埃尔(Richard Gabriel)臭名昭著的“更糟的崛起”中,他理所当然地(并且感到
ans愧

顺便说一下,我很乐意对此进行更正-我对语法和语法了解不多。我将更多地根据历史事实进行推断。
2011年

12

在设计C语言时,可以用计算机的工作方式来解释C语言的许多奇怪之处。存储空间非常有限,因此,最小化源代码文件本身的大小非常重要。上世纪70年代和80年代的编程实践是确保源代码包含尽可能少的字符,最好不要包含过多的源代码注释。

今天,这当然很荒谬,硬盘驱动器上几乎没有无限的存储空间。但这是C一般具有这种奇怪语法的部分原因。


具体而言,关于数组指针,您的第二个示例应该是int (*p)[10];(是的,语法非常混乱)。我可能会将其读为“指向十个数组的整数指针”……这在一定程度上是有意义的。如果没有括号,则编译器会将其解释为由十个指针组成的数组,这将使声明具有完全不同的含义。

由于数组指针和函数指针在C语言中都具有相当晦涩的语法,因此明智的做法是将怪异类型化。也许像这样:

晦涩的例子:

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

非模糊的等效示例:

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

如果您要处理函数指针数组,事情会变得更加晦涩难懂。或其中最晦涩的是:函数返回函数指针(轻度有用)。如果您不使用typedef进行此类操作,您将很快发疯。


嗯,最后是一个合理的答案。:-)我对特定语法实际上如何缩小源代码大小感到好奇,但是无论如何这是一个合理的想法并且有意义。谢谢。+1
user541686

我会说这与源代码大小无关,而与编译器编写有关,而对于“ typdef消除怪异”肯定是+1。我意识到自己可以做到的那一天,我的心理健康得到了极大的改善。
2011年

2
[需要引用]关于源代码大小的东西。我从未听说过这样的限制(尽管也许这是“每个人都知道”的东西)。
肖恩·麦克米伦

1
好吧,我是在70年代用IBM,DEC和XEROX套件上的COBOL,Assembler,CORAL和PL / 1编写程序的,而我从未遇到源代码大小限制。数组大小,可执行文件大小,程序名称大小的限制-但从不限制源代码大小。
詹姆斯·安德森

1
@Sean McMillan:我认为源代码的大小不是一个限制(考虑到当时像Pascal这样的冗长的语言非常流行)。即使是这种情况,我认为预先准备源代码并将长关键字替换为短的一字节代码(例如,以前使用的某些基本解释器)也非常容易。因此,我发现“ C之所以简洁是因为它是在可用内存较少的时期发明的”,它有点弱。
Giorgio

7

这很简单:int *p意味着这*p是一个整数;int a[5]表示这a[i]是一个整数。

int (*f)(int (*a)[5])

表示这*f是一个函数,*a是一个由五个整数组成的数组,因此f一个函数也将获取一个指向五个整数的数组的指针,并返回int。但是,在C语言中,将指针传递给数组没有用。

C声明很少会变得如此复杂。

另外,您可以使用typedef进行说明:

typedef int vec5[5];
int (*f)(vec5 *a);

4
抱歉,这听起来很粗鲁(我不是故意的),但是我认为您错过了问题的全部重点...:\
user541686 2011年

2
@Mehrdad:我无法告诉你Kernighan和Ritchie的想法。我确实告诉过您语法的逻辑。我不了解大多数人,但我认为您建议的语法不太清楚。
凯文·克莱恩

我同意-看到如此复杂的声明是不寻常的。
迦勒

C的设计报关单早typedefconstvolatile,并声明中初始化事物的能力。在最初设计的语言中,不会出现声明语法的许多烦人的歧义(例如,是否int const *p, *q;应绑定const到类型或声明符)。我希望该语言在类型和声明符之间添加一个冒号,但允许在使用不带限定符的内置“保留字”类型时省略该语言。的意义int: const *p,*q;int const *: p,*q;本来清晰。
超级猫

3

我认为您必须将* []视为附加到变量的运算符。*写在变量之前,[]之后。

让我们阅读类型表达式

int* (*p)[10];

最里面的元素是p,是一个变量,因此

p

表示:p是一个变量。

在变量之前有*,*运算符始终放在它所引用的表达式之前,因此,

(*p)

表示:变量p是一个指针。如果没有(),则右边的[]运算符将具有更高的优先级,即

**p[]

将被解析为

*(*(p[]))

下一步是[]:由于没有其他(),[]的优先级高于外部*,因此

(*p)[]

表示:(变量p是指针)到数组。然后有第二个*:

* (*p)[]

意思是:((变量p是一个指针)指向数组的指针)

最后,您拥有int运算符(类型名称),其优先级最低:

int* (*p)[]

意思是:((((变量p是一个指针)指向数组的指针)指向数组的指针)整数。

因此,整个系统基于带有运算符的类型表达式,并且每个运算符都有自己的优先级规则。这允许定义非常复杂的类型。


0

开始思考时并不难,C从来都不是很容易的语言。而且int*[10]* p真的是不容易比int* (*p)[10] 什么k的类型将是int*[10]* p, k;


2
k将是一次失败的代码审查,我可以确定编译器将执行的操作,甚至可能会打扰我,但我无法确定程序员的意图-失败…………
mattnz

以及为什么k代码审查失败?
Dainius

1
因为代码不可读也不可维护。代码是不正确的,显然是正确的,并且在维护后仍可能保持正确。您必须问k是什么类型的事实,表明代码无法满足这些基本要求。
mattnz

1
基本上,在同一行上有3个(在这种情况下)不同类型的变量声明,例如int * p,int i [10]和int k。那是不可接受的。如果变量具有某种形式的关系,例如int宽度,高度,深度;则可以接受相同类型的多个声明。请记住,许多人使用int * p进行编程,所以'int * p,i;'中的i是什么。
mattnz

1
@mattnz想要说的是,您可以随心所欲地聪明,但是当您的意图不明显和/或您的代码编写不佳/不可读时,这一切都没有意义。这种东西通常会导致代码损坏和时间浪费。另外,pointer to intint甚至没有同类型的,所以他们应该另行申报。期。听那个男人。他有18k代表,这是有原因的。
Braden Best,
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.