简体   繁体   English

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

[英]Why does/did C allow implicit function and typeless variable declarations?

Why is it sensible for a language to allow implicit declarations of functions and typeless variables? 为什么语言允许隐式声明函数和无类型变量? I get that C is old, but allowing to omit declarations and default to int() (or int in case of variables) doesn't seem so sane to me, even back then. 我知道C是旧的,但是允许省略声明并且默认为int() (或者在变量的情况下为int )对我来说似乎不太理智,即使在那时也是如此。

So, why was it originally introduced? 那么,为什么最初推出? Was it ever really useful? 它真的有用吗? Is it actually (still) used? 它实际上(仍然)使用过吗?

Note: I realise that modern compilers give you warnings (depending on which flags you pass them), and you can suppress this feature. 注意:我意识到现代编译器会给你警告(取决于你传递它们的标志),你可以抑制这个功能。 That's not the question! 那不是问题!


Example: 例:

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

int foo(int i) {
  return i;
}

It's the usual story — hysterical raisins (aka 'historical reasons'). 这是通常的故事 - 歇斯底里的葡萄干 (又名“历史原因”)。

In the beginning, the big computers that C ran on (DEC PDP-11) had 64 KiB for data and code (later 64 KiB for each). 最初,C运行的大型计算机(DEC PDP-11)具有64 KiB的数据和代码(后来每个64 KiB)。 There was a limit to how complex you could make the compiler and still have it run. 您可以使编译器复杂化并且仍然可以运行它是有限制的。 Indeed, there was scepticism that you could write an O/S using a high-level language such as C, rather than needing to use assembler. 实际上,有人怀疑你可以使用C等高级语言编写O / S,而不是需要使用汇编程序。 So, there were size constraints. 因此,存在尺寸限制。 Also, we are talking a long time ago, in the early to mid 1970s. 此外,我们在很久以前,在20世纪70年代早期到中期谈论。 Computing in general was not as mature a discipline as it is now (and compilers specifically were much less well understood). 一般而言,计算并不像现在这样成熟(并且编译器特别不太了解)。 Also, the languages from which C was derived (B and BCPL) were typeless. 此外,C派生的语言(B和BCPL)是无类型的。 All these were factors. 所有这些都是因素。

The language has evolved since then (thank goodness). 从那以后语言发生了变化(谢天谢地)。 As has been extensively noted in comments and down-voted answers, in strict C99, implicit int for variables and implicit function declarations have both been made obsolete. 正如在评论和缩小的答案中已经广泛注意到的那样,在严格的C99中,变量和隐式函数声明的隐式int都已经过时了。 However, most compilers still recognize the old syntax and permit its use, with more or less warnings, to retain backwards compatibility, so that old source code continues to compile and run as it always did. 但是,大多数编译器仍然认识到旧语法并允许其使用或多或少的警告来保持向后兼容性,以便旧源代码继续像以往一样编译和运行。 C89 largely standardized the language as it was, warts ( gets() ) and all. C89在很大程度上标准化了语言,瑕疵( gets() )等等。 This was necessary to make the C89 standard acceptable. 这对于使C89标准可接受是必要的。

There is still old code around using the old notations — I spend quite a lot of time working on an ancient code base (circa 1982 for the oldest parts) which still hasn't been fully converted to prototypes everywhere (and that annoys me intensely, but there's only so much one person can do on a code base with multiple millions of lines of code). 使用旧的符号仍然有旧的代码 - 我花了很多时间在一个古老的代码库(大约1982年的最古老的部分)工作,但仍然没有完全转换为原型到处(这让我非常恼火,但是,只有一个人可以在具有数百万行代码的代码库上做到这一点。 Very little of it still has 'implicit int ' for variables; 很少有变量的'隐含int '; there are too many places where functions are not declared before use, and a few places where the return type of a function is still implicitly int . 在使用之前没有声明函数的地方太多,以及函数的返回类型仍然隐式为int的一些地方。 If you don't have to work with such messes, be grateful to those who have gone before you. 如果你不必与这些混乱一起工作,那就要感谢那些在你面前的人。

See Dennis Ritchie's "The Development of the C Language": http://cm.bell-labs.com/who/dmr/chist.html 参见Dennis Ritchie的“C语言的发展”: http//cm.bell-labs.com/who/dmr/chist.html

For instance, 例如,

In contrast to the pervasive syntax variation that occurred during the creation of B, the core semantic content of BCPL—its type structure and expression evaluation rules—remained intact. 与在B的创建期间发生的普遍语法变化相反,BCPL的核心语义内容 - 其类型结构和表达评估规则 - 保持不变。 Both languages are typeless, or rather have a single data type, the 'word', or 'cell', a fixed-length bit pattern. 两种语言都是无类型的,或者更确切地说是单一数据类型,即“单词”或“单元格”,固定长度的位模式。 Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. 这些语言中的存储器由这种单元的线性阵列组成,并且单元内容的含义取决于所应用的操作。 The + operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands. 例如,+运算符只是使用机器的整数加法指令添加其操作数,而其他算术运算同样不知道其操作数的实际含义。 Because memory is a linear array, it is possible to interpret the value in a cell as an index in this array, and BCPL supplies an operator for this purpose. 因为内存是一个线性数组,所以可以将单元格中的值解释为此数组中的索引,并且BCPL为此提供运算符。 In the original language it was spelled rv, and later !, while B uses the unary *. 在原始语言中,它拼写为rv,稍后!,而B使用一元*。 Thus, if p is a cell containing the index of (or address of, or pointer to) another cell, *p refers to the contents of the pointed-to cell, either as a value in an expression or as the target of an assignment. 因此,如果p是包含另一个单元格的索引(或地址或指针)的单元格,则* p指向指向单元格的内容,作为表达式中的值或作为赋值的目标。

This typelessness persisted in C until the authors started porting it to machines with different word lengths: 这种无类型在C中持续存在,直到作者开始将其移植到具有不同字长的机器上:

The language changes during this period, especially around 1977, were largely focused on considerations of portability and type safety, in an effort to cope with the problems we foresaw and observed in moving a considerable body of code to the new Interdata platform. 在此期间,尤其是1977年左右,语言的变化主要集中在可移植性和类型安全性的考虑上,以努力应对我们在将大量代码移植到新的Interdata平台时所预见和观察到的问题。 C at that time still manifested strong signs of its typeless origins. 当时的C仍然表现出其无类型起源的强烈迹象。 Pointers, for example, were barely distinguished from integral memory indices in early language manuals or extant code; 例如,指针与早期语言手册或现存代码中的整体记忆指数几乎没有区别; the similarity of the arithmetic properties of character pointers and unsigned integers made it hard to resist the temptation to identify them. 字符指针和无符号整数的算术属性的相似性使得难以抵抗识别它们的诱惑。 The unsigned types were added to make unsigned arithmetic available without confusing it with pointer manipulation. 添加了无符号类型以使无符号算术可用,而不会将其与指针操作混淆。 Similarly, the early language condoned assignments between integers and pointers, but this practice began to be discouraged; 同样,早期语言宽恕整数和指针之间的分配,但这种做法开始被劝阻; a notation for type conversions (called `casts' from the example of Algol 68) was invented to specify type conversions more explicitly. 发明了类型转换的表示法(在Algol 68的例子中称为“强制转换”),以更明确地指定类型转换。 Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; 被PL / I的例子所迷惑,早期的C没有将结构指针牢牢地绑定到它们所指向的结构,并且允许程序员几乎不考虑指针的类型来编写指针 - >成员; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type. 这样的表达式不加批判地作为对指针指定的内存区域的引用,而成员名称仅指定了偏移量和类型。

Programming languages evolve as programming practices change. 编程语言随着编程实践的变化而演变。 In modern C and the modern programming environment, where many programmers have never written assembly language, the notion that ints and pointers are interchangeable may seem nearly unfathomable and unjustifiable. 在现代C和现代编程环境中,许多程序员从未编写过汇编语言,内联和指针可以互换的概念看起来几乎是不可思议和不合理的。

Probably the best explanation for "why" comes from here : 可能对“为什么”的最佳解释来自这里

Two ideas are most characteristic of C among languages of its class: the relationship between arrays and pointers, and the way in which declaration syntax mimics expression syntax. 在类的语言中,两个思想是C的最大特征:数组和指针之间的关系,以及声明语法模仿表达式语法的方式。 They are also among its most frequently criticized features, and often serve as stumbling blocks to the beginner. 它们也是最常被批评的特征之一,并且经常成为初学者的绊脚石。 In both cases, historical accidents or mistakes have exacerbated their difficulty. 在这两种情况下,历史事故或错误都加剧了他们的困难。 The most important of these has been the tolerance of C compilers to errors in type. 其中最重要的是C编译器对类型错误的容忍度。 As should be clear from the history above, C evolved from typeless languages . 从上面的历史中可以清楚地看出,C是从无类型语言演变而来的 It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; 它最初的用户和开发者并没有突然认为它是一种全新的语言,并且有自己的规则; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code. 相反,我们不得不将现有的程序作为所开发的语言进行调整,并允许现有的代码体。 (Later, the ANSI X3J11 committee standardizing C would face the same problem.) (后来,标准化C的ANSI X3J11委员会将面临同样的问题。)

Systems programming languages don't necessarily need types; 系统编程语言不一定需要类型; you're mucking around with bytes and words, not floats and ints and structs and strings. 你正在乱搞字节和单词,而不是浮点数和整数和结构和字符串。 The type system was grafted onto it in bits and pieces, rather than being part of the language from the very beginning. 类型系统被一点一滴地嫁接到它上面,而不是从一开始就成为语言的一部分。 As C has moved from being primarily a systems programming language to a general-purpose programming language, it has become more rigorous in how it handles types. 随着C从主要是系统编程语言转变为通用编程语言,它在处理类型方面变得更加严格。 But, even though paradigms come and go, legacy code is forever. 但是,即使范式来来去去,遗留代码也是永恒的。 There's still a lot of code out there that relies on that implicit int , and the standards committee is reluctant to break anything that's working. 还有很多代码依赖于隐含的int ,标准委员会不愿意破坏任何有效的代码。 That's why it took almost 30 years to get rid of it. 这就是为什么花了将近30年才摆脱它。

A long, long time ago, back in the K&R, pre-ANSI days, functions looked quite different than they do today. 很久很久以前,回到K&R,在ANSI之前的日子里,功能看起来与今天完全不同。

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

int ansi_add_numbers(int x, int y); // modern, ANSI C

When you call a function like add_numbers , there is an important difference in the calling conventions: all types are "promoted" when the function is called. 当你调用像add_numbers这样的函数时,调用约定有一个重要的区别:调用函数时所有类型都被“提升”。 So if you do this: 所以,如果你这样做:

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

What happens is x is promoted to int , y is promoted to int , and the return type is assumed to be int by default. 发生的是x被提升为inty被提升为int ,并且默认情况下返回类型被假定为int Likewise, if you pass a float it is promoted to double. 同样,如果你传递一个float它会被提升为double。 These rules ensured that prototypes weren't necessary, as long as you got the right return type, and as long as you passed the right number and type of arguments. 这些规则确保原型不是必需的,只要您获得正确的返回类型,并且只要您传递正确的数字和类型的参数。

Note that the syntax for prototypes is different: 请注意,原型的语法是不同的:

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

A common practice back in the old days was to avoid header files for the most part, and just stick the prototypes directly in your code: 过去常见的做法是大部分都避免使用头文件,只需将原型直接粘贴在代码中:

void *malloc();

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

Header files are accepted as a necessary evil in C these days, but just as modern C derivatives (Java, C#, etc.) have gotten rid of header files, old-timers didn't really like using header files either. 如今,头文件被认为是C中必不可少的恶魔,但正如现代C衍生物(Java,C#等)已经摆脱了头文件一样,老人也不喜欢使用头文件。

Type safety 类型安全

From what I understand about the old old days of pre-C, there wasn't always much of a static typing system. 据我了解关于预-C的时光,也并不总是太大的静态类型系统。 Everything was an int , including pointers. 一切都是int ,包括指针。 In this old language, the only point of function prototypes would be to catch arity errors. 在这种古老的语言中,函数原型的唯一要点就是捕获arity错误。

So if we hypothesize that functions were added to the language first, and then a static type system was added later, this theory explains why prototypes are optional. 因此,如果我们假设首先将函数添加到语言中,然后再添加静态类型系统,则该理论解释了为什么原型是可选的。 This theory also explains why arrays decay to pointers when used as function arguments -- since in this proto-C, arrays were nothing more than pointers which get automatically initialized to point to some space on the stack. 这个理论也解释了为什么数组在用作函数参数时会衰减为指针 - 因为在这个原型C中,数组只不过是指针自动初始化为指向堆栈上的某个空间。 For example, something like the following may have been possible: 例如,可能有类似以下内容:

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

Citations 引文

On typelessness: 无类型:

Both languages [B and BCPL] are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. 两种语言[B和BCPL]都是无类型的,或者更确切地说是单个数据类型,即“单词”或“单元格”,这是一种固定长度的位模式。

On the equivalence of integers and pointers: 关于整数和指针的等价:

Thus, if p is a cell containing the index of (or address of, or pointer to) another cell, *p refers to the contents of the pointed-to cell, either as a value in an expression or as the target of an assignment. 因此,如果p是包含另一个单元格的索引(或地址或指针)的单元格,则*p指向指向单元格的内容,作为表达式中的值或作为赋值的目标。

Evidence for the theory that prototypes were omitted due to size constraints: 由于尺寸限制而省略原型的理论证据:

During development, he continually struggled against memory limitations: each language addition inflated the compiler so it could barely fit, but each rewrite taking advantage of the feature reduced its size. 在开发期间,他不断努力克服内存限制:每个语言添加都会使编译器膨胀,因此它几乎不适合,但每次重写都会利用该功能减小其大小。

Some food for thought. 一些思考的食物。 (It's not an answer; we actually know the answer — it's permitted for backward compatibility.) (这不是答案;我们实际上知道答案 - 允许向后兼容。)

And people should look at COBOL code base or f66 libraries before saying why it's not cleaned up in 30 years or so! 人们应该先看看COBOL代码库或f66库,然后再说明为什么它在30年左右的时间里没有清理干净!

gcc with its default does not spit out any warnings. gcc默认不会吐出任何警告。

With -Wall and gcc -std=c99 do spit out the correct thing -Wallgcc -std=c99确实吐出正确的东西

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

The lint functionality built into modern gcc is showing its color. 现代gcc内置的lint功能正在显示其颜色。

Interestingly the modern clone of lint , the secure lint — I mean splint — gives only one warning by default. 有趣的是现代的lint克隆,安全的lint - 我的意思是splint - 默认情况下只提供一个警告。

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

The llvm C compiler clang which also has a static analyser built into it like gcc , spits out the two warnings by default. 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()"
         ^

People used to think we don't need backward compatibility for 80's stuff. 过去人们认为我们不需要向后兼容80的东西。 All the code must be cleaned up or replaced. 必须清理或更换所有代码。 But it turns out it's not the case. 但事实证明并非如此。 A lot of production code stays in prehistoric non-standard times. 许多生产代码都处于史前非标准时代。

EDIT: 编辑:

I didn't look through other answers before posting mine. 在发帖之前我没有通过其他答案。 I may have misunderstood the intention of the poster. 我可能误解了海报的意图。 But the thing is there was a time when you hand compiled your code, and use toggle to put the binary pattern in memory. 但事情是,有一段时间你手工编译你的代码,并使用切换将二进制模式放在内存中。 They didn't need a "type system". 他们不需要“类型系统”。 Nor does the PDP machine in front of which Richie and Thompson posed like this : 在Richie和Thompson面前的PDP机器也不是这样的:

Don't look at the beard, look at the "toggles", which I heard were used to bootstrap the machine. 不要看胡子,看看“乞丐”,我听说这是用来引导机器的。

K&R

And also look how they used to boot UNIX in this paper. 还看看他们在本文中如何使用UNIX来启动UNIX。 It's from the Unix 7th edition manual. 它来自Unix第7版手册。

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

The point of the matter is they didn't need so much software layer managing a machine with KB sized memory. 问题的关键是他们不需要那么多软件层来管理具有KB大小内存的机器。 Knuth's MIX has 4000 words. Knuth的MIX有4000个单词。 You don't need all these types to program a MIX computer. 您不需要所有这些类型来编程MIX计算机。 You can happily compare a integer with pointer in a machine like this. 您可以愉快地在这样的机器中将整数与指针进行比较。

I thought why they did this is quite self-evident. 我想为什么他们这样做是不言而喻的。 So I focused on how much is left to be cleaned up . 所以我专注于要清理多少

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C11 是否允许在函数的任何地方声明变量? - Does C11 allow variable declarations at any place in a function? C 中的隐式函数声明 - Implicit function declarations in C 隐式函数声明和链接 - Implicit function declarations and linkage 为什么 C 允许使用枚举作为变量? - Why does C allow the use of enum as a variable? 为什么 C 的 BNF 语法允许使用空的 init-declarators 序列进行声明? - Why does C's BNF grammar allow declarations with an empty sequence of init-declarators? ANSI C函数声明中隐式int返回类型的规则 - Rules for implicit int return type in ANSI C function declarations 为什么在main中说函数的隐式声明? C程序设计 - Why does it say implicit declaration of function in main? C programming 为什么C不允许从char **到const char * const *(和C ++)的隐式转换? - Why C doesn't allow implicit conversion from char ** to const char *const * (and C++ does)? 将变量声明放在C中的单独函数中 - Putting variable declarations in a separate function in C ANSI C-直接声明符语法-为什么C语法允许在语法上合法但在意义上是非法的声明,例如int func()()? - ANSI C - direct-declarator grammar - Why does the C grammar allow syntactically legal, but sementically illegal declarations like int func()()?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM