ANSI C和K&R C之间的主要区别是什么?

48
维基百科关于 ANSI C 的文章写道:

ANSI C 标准化过程的一个目标是生成 K&R C 的超集(第一个发布的标准),吸收了许多随后引入的非官方功能。然而,标准委员会也包含了一些新特性,比如从 C++ 编程语言借鉴来的函数原型和更强大的预处理器。参数声明的语法也被改变以反映 C++ 风格。

这让我想到它们之间存在差异。然而,我没有看到 K&R C 和 ANSI C 之间的比较。是否有这样的文档?如果没有,主要区别是什么?

编辑:我相信 K&R 书上写着“ANSI C”。至少我家里的版本是这样的。那么现在可能没有什么区别了吧?


3
你的书是K&R的第二版;当提到K&R C时,它指的是第一版中描述的C语言(在ANSI标准出现之前,这本书曾经作为一种标准,但那时候语言已经有了相当大的分歧)。 - vonbrand
11个回答

33

关于“K&R C”的定义可能存在一些混淆。这个术语指的是《C程序设计语言》第一版中记录的语言。粗略地说,它是贝尔实验室C编译器大约在1978年左右使用的输入语言。

Kernighan和Ritchie参与了ANSI标准化过程。 "ANSI C"方言取代了“K&R C”,随后的《C程序设计语言》版本采用了ANSI惯例。“K&R C”是一种“死亡语言”,除了某些编译器仍接受旧代码外,已经不再使用。


4
K&R的第一版于1978年出版。C语言在1969年至1978年之间发生了很大的变化。 - Keith Thompson
先生,我在您的答案中添加了一部分,因为我不确定是否可以将其作为另一个答案添加,您可以删除它,您对此有何看法? - Suraj Jain

19

函数原型是K&R C和C89之间最明显的变化,但还有很多其他变化。标准化C库也做了很多重要的工作。尽管标准C库是现有实践的编码,但它编码了多个现有实践,这使得它更加困难。P.J. Plauger的书《The Standard C Library》是一个很好的参考,并且还讲述了图书馆为什么以其目前的方式结束的幕后细节。

ANSI / ISO标准C在大多数方面与K&R C非常相似。它旨在大多数现有C代码都应该在ANSI编译器上构建而不需要太多更改。然而,在预标准时代,语言的语义对于每个编译器供应商都是开放的解释。 ANSI C引入了一种语言语义的通用描述,将所有编译器置于同等地位。现在,大约20年后,我们很容易认为这是一个重要的成就。

基本上,如果您没有要维护的预先标准C代码库,那么您应该庆幸不必担心它。如果您确实有一个预先标准的代码库要维护,或者更糟的是,如果您正在试图将旧程序升级到更现代的标准,则我对您表示同情。


14

有一些细微差别,但我认为K&R的后续版本是针对ANSI C的,因此现在没有真正的区别了。
" C Classic "(缺乏更好的术语)定义函数的方式略有不同,即

int f( p, q, r )  
int p, float q, double r;  
{  
    // Code goes here  
}

我认为另一个区别在于函数原型。原型不能-事实上也不可能-带有参数或类型列表。在ANSI C中,它们可以。


5
严格来说,“原型”按照定义是带有显式类型参数的函数声明。那些旧式 K&R 风格的声明不是原型。 - AnT stands with Russia
4
那不符合K&R标准 :) 应该写成int p; float q; double r;或者int p, q, r; - Seva Alekseyev

7
  1. 函数原型。
  2. 常量和易失性修饰符。
  3. 宽字符支持和国际化。
  4. 允许使用函数指针而不需要解引用。

3

另一个不同之处在于,函数的返回类型和参数类型不需要定义。它们将被假定为整数。

f(x)
{
    return x + 1;
}

并且

int f(x)
int x;
{
    return x + 1;
}

是完全相同的。


2
ANSI C仍允许使用默认int类型。 - Corey D

3
主要区别在于ANSI C与K&R C的差异如下:
  • 函数原型
  • 支持const和volatile数据类型限定符
  • 支持宽字符和国际化
  • 允许使用函数指针而不需要解除引用
ANSI C采用了C++函数原型技术,其中函数定义和声明包括函数名称、参数数据类型和返回值数据类型。函数原型使得ANSI C编译器能够检查用户程序中的函数调用是否传递了无效数量的参数或不兼容的参数数据类型。这修复了K&R C编译器的主要弱点。
例如:声明一个名为foo的函数,并要求foo接受两个参数。
 unsigned long foo (char* fmt, double data)
 {
      /*body of foo */
 }

2
  • 函数原型:ANSI C采用了C++函数原型技术,其中函数定义和声明包括函数名称、参数类型、数据类型和返回值数据类型。函数原型使得ANSI C编译器能够检查用户程序中的函数调用,以避免传递无效数量的参数或不兼容的参数数据类型。这纠正了K&R C编译器的一个主要弱点:在用户程序中进行的无效调用通常可以编译通过,但在执行时会导致程序崩溃。

2
一个重要的区别是,在ANSI出现之前,C语言主要通过先例而非规范来定义;在某些情况下,某些操作在一些平台上会有可预测的后果,但在其他平台上则不然(例如,在两个不相关指针上使用关系运算符),先例强烈倾向于使平台保证对程序员可用。例如:
1. 在定义所有对象的所有指针之间的自然排序的平台上,可以依赖于将任意指针应用于关系运算符来产生该排序。 2. 在自然测试一个指针是否“大于”另一个的平台上,除了产生真或假值外,也可以依赖于将关系运算符应用于任意指针,同样不会产生任何副作用。 3. 在两个或多个整数类型共享相同大小和表示的平台上,可以依赖于指向任何这种整数类型的指针读取或写入具有相同表示的任何其他类型的信息。 4. 在二进制补码平台上,整数溢出自然地静默地包装,涉及小于“int”的无符号值的操作可以依赖于在结果介于INT_MAX+1u和UINT_MAX之间时,它的行为就像该值是无符号的,并且它没有被提升为更大的类型,也没有被用作>>的左操作数,也没有被用作/%或任何比较运算符的任一操作数。顺便说一下,标准的理由之一是小的无符号类型升级为有符号类型。
在C89之前,对于那些上述假设不自然成立的平台,编译器可能会采取什么措施来支持这些假设还不清楚,但是毫无疑问,在可以轻松且廉价地支持这些假设的平台上,编译器应该这样做。 C89标准的作者没有特别说明这一点,因为:
1. 没有故意使人迟钝的编译器的编写者将继续在实际情况下执行此类操作(为小的无符号值升级为有符号的原因强烈强调了这种观点)。 2. 标准只要求实现能够运行一个可能是虚构的程序而没有堆栈溢出,并认识到虽然迟钝的实现可以将任何其他程序视为调用Undefined Behavior,但它并不认为担心迟钝的编译器编写者编写“符合”但无用的实现是值得的。
尽管“C89”当代被解释为“由C89定义的语言,加上平台提供的任何其他功能和保证”,但gcc的作者一直在推动一种解释,该解释排除了C89规定之外的任何功能和保证。

先生,我需要和您谈论这个问题,因为我计划开始阅读K&R第二版。我们可以聊聊吗? - Suraj Jain
@SurajJain:如果您打开一个聊天会话,我会尽力回复。 - supercat

2
区别在于:
  1. 原型
  2. 宽字符支持和国际化
  3. 支持const和volatile关键字
  4. 允许函数指针用作解引用

1
我认为最大的单一区别是函数原型和描述函数参数类型的语法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接