为什么/ C允许隐式函数和无类型变量声明?

为什么语言允许隐式声明函数和无类型变量? 我知道C是旧的,但是允许省略声明并且默认为int() (或者在变量的情况下为int )对我来说似乎并不那么理智,即使在那时也是如此。

那么,为什么它最初被引入? 它真的有用吗? 它实际上(仍然)使用过吗?

注意:我意识到现代编译器会给你警告(取决于你传递它们的标志),你可以抑制这个function。 那不是问题!


例:

 int main() { static bar = 7; // defaults to "int bar" return foo(bar); // defaults to a "int foo()" } int foo(int i) { return i; } 

请参阅Dennis Ritchie的“C语言的发展”: http : //cm.bell-labs.com/who/dmr/chist.html

例如,

与在创建B期间发生的普遍语法变化相反,BCPL的核心语义内容 – 其类型结构和表达式评估规则 – 保持不变。 两种语言都是无类型的,或者更确切地说是单一数据类型,即“单词”或“单元格”,固定长度的位模式。 这些语言中的存储器由这种单元的线性arrays组成,并且单元内容的含义取决于所应用的操作。 例如,+运算符只是使用机器的整数加法指令添加其操作数,而其他算术运算同样不知道其操作数的实际含义。 因为内存是一个线性数组,所以可以将单元格中的值解释为此数组中的索引,并且BCPL为此提供运算符。 在原始语言中,它拼写为rv,稍后!,而B使用一元*。 因此,如果p是包含另一个单元的索引(或地址或指针)的单元格,则* p指的是指向单元格的内容,可以是表达式中的值,也可以是赋值的目标。

这种无类型在C中持续存在,直到作者开始将其移植到具有不同字长的机器上:

在此期间,特别是1977年左右,语言的变化主要集中在可移植性和类型安全性的考虑上,以便应对我们在将大量代码移植到新的Interdata平台时所预见和观察到的问题。 当时的C仍然表现出其无类型起源的强烈迹象。 例如,指针与早期语言手册或现存代码中的整体记忆指数几乎没有区别; 字符指针和无符号整数的算术属性的相似性使得很难抵抗识别它们的诱惑。 添加了无符号类型以使无符号算术可用,而不会将其与指针操作混淆。 同样,早期的语言宽恕整数和指针之间的分配,但这种做法开始被劝阻; 发明了类型转换的表示法(在Algol 68的例子中称为“强制转换”),以更明确地指定类型转换。 在PL / I的例子中,早期的C没有将结构指针牢牢地绑定到它们指向的结构,并且允许程序员几乎不考虑指针的类型来编写指针 – >成员; 这样的表达式不加批判地作为对指针指定的内存区域的引用,而成员名称仅指定了偏移量和类型。

编程语言随着编程实践的变化而演变。 在现代C和现代编程环境中,许多程序员从未编写过汇编语言,int和指针可以互换的概念看起来几乎是不可思议和不合理的。

这是通常的故事 – 歇斯底里的葡萄干 (又名“历史原因”)。

最初,C运行的大型计算机(DEC PDP-11)的数据和代码为64 KiB(后来为每个64 KiB)。 您可以使编译器复杂化并且仍然可以运行它是有限制的。 实际上,有人怀疑你可以使用C等高级语言编写O / S,而不是需要使用汇编程序。 因此,存在尺寸限制。 此外,我们在很久以前,在20世纪70年代早期到中期谈论。 一般而言,计算并不像现在这样成熟(并且编译器特别不太了解)。 此外,C派生的语言(B和BCPL)是无类型的。 所有这些都是因素。

从那以后语言发生了变化(谢天谢地)。 正如在评论和缩小的答案中已经广泛注意到的那样,在严格的C99中,变量和隐式函数声明的隐式int都已经过时了。 但是,大多数编译器仍然认识到旧的语法并允许其使用或多或少的警告来保持向后兼容性,以便旧的源代码继续像往常一样编译和运行。 C89在很大程度上标准化了语言,瑕疵( gets() )等等。 这对于使C89标准可接受是必要的。

使用旧符号仍然有很旧的代码 – 我花了很多时间在一个古老的代码库上工作(大约1982年用于最古老的部分)仍然没有完全转换为原型到处(这让我非常恼火,但是只有一个人可以在具有数百万行代码的代码库上做到这么多。 很少有变量的’隐含int ‘; 在使用之前没有声明函数的地方太多,以及函数的返回类型仍然隐式为int的一些地方。 如果你不必与这些混乱一起工作,那就要感谢那些在你面前的人。

可能对“为什么”的最佳解释来自这里 :

在类的语言中,两个想法是C的最大特征:数组和指针之间的关系,以及声明语法模仿表达式语法的方式。 它们也是最常被批评的特征之一,并且经常成为初学者的绊脚石。 在这两种情况下,历史事故或错误都加剧了他们的困难。 其中最重要的是C编译器对类型错误的容忍度。 从上面的历史中可以清楚地看出,C是从无类型语言演变而来的 。 它最初的用户和开发人员并没有突然认为它是一种全新的语言,并且有自己的规则; 相反,我们不得不根据所开发的语言调整现有程序,并允许现有的代码体。 (后来,标准化C的ANSI X3J11委员会将面临同样的问题。)

系统编程语言不一定需要类型; 你正在捣乱字节和单词,而不是浮点数和整数和结构和字符串。 类型系统被一点一滴地嫁接到它上面,而不是从一开始就成为语言的一部分。 随着C从主要是系统编程语言转变为通用编程语言,它在处理类型方面变得越来越严格。 但是,即使范式来来去去,遗留代码也是永恒的。 还有很多代码依赖于隐含的int ,标准委员会不愿意破坏任何有效的代码。 这就是为什么花了将近30年才摆脱它。

很久很久以前,回到K&R,在ANSI之前的日子里,function看起来与今天完全不同。

 add_numbers(x, y) { return x + y; } int ansi_add_numbers(int x, int y); // modern, ANSI C 

当你调用像add_numbers这样的函数时,调用约定有一个重要的区别:调用函数时所有类型都被“提升”。 所以,如果你这样做:

 // no prototype for add_numbers short x = 3; short y = 5; short z = add_numbers(x, y); 

会发生什么是x被提升为inty被提升为int ,并且默认情况下返回类型被假定为int 。 同样,如果你传递一个float它会被提升为double。 这些规则确保原型不是必需的,只要您获得正确的返回类型,并且只要您传递正确的数量和类型的参数。

请注意,原型的语法是不同的:

 // K&R style function // number of parameters is UNKNOWN, but fixed // return type is known (int is default) add_numbers(); // ANSI style function // number of parameters is known, types are fixed // return type is known int ansi_add_numbers(int x, int y); 

过去常见的做法是大部分避免使用头文件,只需将原型直接粘贴在代码中:

 void *malloc(); char *buf = malloc(1024); if (!buf) abort(); 

如今,头文件被认为是C中必不可少的恶魔,但正如现代C衍生物(Java,C#等)已经摆脱了头文件一样,老人也不喜欢使用头文件。

类型安全

根据我对前C的旧时代的理解,并不总是有很多静态打字系统。 一切都是int ,包括指针。 在这种古老的语言中,函数原型的唯一要点就是捕获arity错误。

因此,如果我们假设首先将函数添加到语言中,然后再添加静态类型系统,则该理论解释了为什么原型是可选的。 这个理论也解释了为什么数组在用作函数参数时会衰减为指针 – 因为在这个原型C中,数组只不过是指针自动初始化为指向堆栈上的某个空间。 例如,可能有类似以下内容:

 function() { auto x[7]; x += 1; } 

引文

  • C语言的发展,Dennis M. Ritchie

无类型:

两种语言[B和BCPL]都是无类型的,或者更确切地说是单个数据类型,即“单词”或“单元格”,这是一种固定长度的位模式。

关于整数和指针的等价:

因此,如果p是包含另一个单元的索引(或地址或指针)的单元格,则*p指的是指向单元格的内容,可以是表达式中的值,也可以是赋值的目标。

由于尺寸限制而省略原型的理论证据:

在开发过程中,他不断努力克服内存限制:每次添加语言都会使编译器膨胀,因此它几乎不适合,但每次重写都会利用该function减小其大小。

一些思考的食物。 (这不是答案;我们实际上知道答案 – 允许向后兼容。)

人们应该先看看COBOL代码库或f66库,然后再说明为什么它在30年左右没有清理干净!

具有默认值的gcc不会吐出任何警告。

-Wallgcc -std=c99确实吐出正确的东西

 main.c:2: warning: type defaults to 'int' in declaration of 'bar' main.c:3: warning: implicit declaration of function 'foo' 

现代gcc内置的lintfunction正在显示其颜色。

有趣的是,现代的lint克隆,安全的lint – 我的意思是splint – 默认只提供一个警告。

 main.c:3:10: Unrecognized identifier: foo Identifier used in code has not been declared. (Use -unrecog to inhibit warning) 

llvm C编译器clang也有内置的静态分析器,如gcc ,默认情况下吐出两个警告。

 main.c:2:10: warning: type specifier missing, defaults to 'int' [-Wimplicit-int] static bar = 7; // defaults to "int bar" ~~~~~~ ^ main.c:3:10: warning: implicit declaration of function 'foo' is invalid in C99 [-Wimplicit-function-declaration] return foo(bar); // defaults to a "int foo()" ^ 

人们过去认为我们不需要向后兼容80年代的东西。 必须清理或更换所有代码。 但结果却并非如此。 许多生产代码都处于史前非标准时代。

编辑:

在发帖之前我没有通过其他答案。 我可能误解了海报的意图。 但事情是,有一段时间你手工编译你的代码,并使用切换将二进制模式放在内存中。 他们不需要“类型系统”。 在Richie和Thompson面前的PDP机器也不是这样的:

不要看胡子,看看“乞丐”,我听说这是用来引导机器的。

K&R

还看看他们在本文中如何用于启动UNIX。 它来自Unix第7版手册。

http://wolfram.schneider.org/bsd/7thEdManVol2/setup/setup.html

问题的关键是他们不需要那么多软件层来管理具有KB大小内存的机器。 Knuth的MIX有4000个单词。 您不需要所有这些类型来编程MIX计算机。 您可以愉快地在这样的机器中将整数与指针进行比较。

我想他们为什么这样做是不言而喻的。 所以我专注于要清理多少