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

时间:2012-08-06 19:54:52

标签: 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;
}

5 个答案:

答案 0 :(得分:12)

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

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

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

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

答案 1 :(得分:11)

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

例如,

  

与在此期间发生的普遍语法变化形成对比   B的创建,BCPL的核心语义内容 - 它的类型结构   和表达评估规则 - 保持不变。两种语言都是   无类型,或者更确切地说是单个数据类型,“单词”或“单元格”,a   固定长度的比特模式。这些语言的记忆由一个   这种细胞的线性阵列,以及细胞内容的含义   取决于所应用的操作。例如,+运算符   使用机器的整数加法指令添加其操作数   其他算术运算同样是无意识的   他们的操作数的含义。因为内存是一个线性数组,所以它是   可以将单元格中的值解释为此数组中的索引,   和BCPL为此目的提供运营商。在原来的   语言是拼写rv,后来!,而B使用一元*。   因此,如果p是包含索引(或地址)的单元格   指向另一个单元格,* p指向指向的内容   cell,作为表达式中的值或作为目标   分配

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

  

在此期间,尤其是1977年左右,语言的变化主要集中在便携性和类型安全方面,   努力应对我们预见和观察到的问题   将大量代码移动到新的Interdata平台。猫   那段时间仍然表现出其无类型起源的强烈迹象。   例如,指针几乎与整体记忆不同   早期语言手册或现存代码中的索引;相似性   字符指针和无符号整数的算术属性   很难抵制识别他们的诱惑。未签名的   添加了类型以使无符号算术可用   将它与指针操作混淆。同样,早期的语言   宽恕和指针之间的宽容赋值,但这种做法   开始气馁;类型转换的表示法(称为   发明了来自Algol 68的例子的'casts'来指定类型   更明确地转换。被早期C的PL / I的例子所迷惑   没有将结构指针牢牢地绑在他们指出的结构上   to,并允许程序员几乎不用编写指针 - >成员   关于指针的类型;这样的表达被采取了   不加批判地作为对由指定的存储区域的引用   指针,而成员名称只指定了一个偏移量和一个类型。

随着编程实践的变化,编程语言也在不断发展。在现代C和现代编程环境中,许多程序员从未编写汇编语言,int和指针可互换的概念可能看起来几乎不可理解且毫无道理。

答案 2 :(得分:7)

“为什么”的最佳解释可能来自here

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

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

答案 3 :(得分:3)

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

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,它会被提升为双倍。这些规则确保原型不是必需的,只要您获得正确的返回类型,并且只要您传递正确的数量和类型的参数。

请注意原型的语法不同:

// 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;
}

引文

关于无类型:

  

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

关于整数和指针的等价:

  

因此,如果p是包含另一个单元格的索引(或地址或指针)的单元格,则*p引用指向单元格的内容,或者作为值在表达中或作为作业的目标。

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

  

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

答案 4 :(得分:1)

有些值得深思。 (这不是答案;我们实际上知道答案 - 允许向后兼容。)

人们应该先看看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’

现代lint内置的gcc功能正在显示其颜色。

有趣的是,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计算机。您可以愉快地在这样的机器中将整数与指针进行比较。

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