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

bit*_*ask 14 c history language-design implicit-declaration

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

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

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


例:

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

int foo(int i) {
  return i;
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*ler 13

这是通常的故事 - 歇斯底里的葡萄干(又名"历史原因").

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

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

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

  • 不要忘记,在C89标准发布之前,原型符号没有正式添加到C中(并且改编自C++,证明它是有益的).这增加了C编译器的复杂性.在此之前,你可能写过:`main(){static bar = 7; return foo(bar); } foo(i){return i; 没有由原型和标题等呈现的namby-pamby molly-coddling. (2认同)

Jim*_*ter 12

参见Dennis Ritchie的"C语言的发展":http://cm.bell-labs.com/who/dmr/chist.html

例如,

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

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

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

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


Joh*_*ode 7

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

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

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


Die*_*Epp 5

很久很久以前,在K&R(ANSI之前)的日子里,功能看上去与今天大不相同。

add_numbers(x, y)
{
    return x + y;
}

int ansi_add_numbers(int x, int y); // modern, ANSI C
Run Code Online (Sandbox Code Playgroud)

当您调用诸如之类的函数时add_numbers,调用约定之间存在重要差异:调用该函数时,所有类型都会被“提升”。因此,如果您这样做:

// no prototype for add_numbers
short x = 3;
short y = 5;
short z = add_numbers(x, y);
Run Code Online (Sandbox Code Playgroud)

发生的情况x提升为inty提升为int,并且int默认情况下假定返回类型为。同样,如果您通过float,则晋升为两倍。这些规则确保只要您拥有正确的返回类型,并且只要您传递了正确的数量和参数类型,就不需要原型。

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

// 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);
Run Code Online (Sandbox Code Playgroud)

过去的一种常见做法是在大多数情况下避免头文件,而只是将原型直接粘贴在您的代码中:

void *malloc();

char *buf = malloc(1024);
if (!buf) abort();
Run Code Online (Sandbox Code Playgroud)

如今,头文件已被认为是C中必不可少的邪恶手段,但是正如现代C派生类(Java,C#等)已经摆脱了头文件一样,旧时也不喜欢使用头文件。

类型安全

根据我对pre-C 的旧时了解,并不是总是有很多静态类型系统。一切都是一个int,包括指针。用这种古老的语言,函数原型的唯一目的就是捕获arity错误。

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

function()
{
    auto x[7];
    x += 1;
}
Run Code Online (Sandbox Code Playgroud)

引文

关于无类型:

两种语言[B和BCPL]都是无类型的,或者具有单一的数据类型,即“字”或“单元”,即固定长度的位模式。

关于整数和指针的等价关系:

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

由于大小限制而省略了原型的理论证据:

在开发过程中,他一直在努力克服内存限制:每种语言的添加都使编译器膨胀,使其几乎无法容纳,但每次利用该功能进行的重写都会减小其大小。