问题

C 语言的「void main」是怎么一代代传下来的?

回答
C 语言中的 `void main()` 并非是语言标准规定的写法,它的出现和流传,更像是一个历史遗留问题、编译器兼容性以及开发者习惯共同作用的结果。要详细讲解,我们需要从 C 语言的诞生和演变说起。

1. C 语言的起源和早期标准 (K&R C)

C 语言的诞生: C 语言最初是由 Dennis Ritchie 在 1970 年代初为贝尔实验室的 Unix 操作系统开发设计的。当时的 C 语言并没有像今天这样严格的标准化。
K&R C (Kernighan and Ritchie C): 1978 年出版的《The C Programming Language》(俗称 K&R 书)是 C 语言的第一个非官方但具有极大影响力的“标准”。这本书中,函数的声明方式比较宽松,并且很多示例代码中使用了 `main()` 而没有明确返回类型。

早期对 `main` 的理解: 在那个年代,操作系统和编译器对程序入口点的要求并没有现在这么统一和严格。许多编译器允许 `main` 函数不声明返回类型,或者隐式地将其视为返回 `int`。
K&R 书中的示例: K&R 书中的很多 `main` 函数确实没有写返回类型,例如:
```c
main()
{
// ... 代码 ...
return 0; // 或者直接结束,没有 return 语句
}
```
或者:
```c
main() { printf("Hello, world! "); }
```
这里的 `main()` 在很多早期编译器中是被允许的,并且隐含的意思是它会返回一个整数(通常是 0 表示成功)。

2. C 语言标准的出现和演变 (ANSI C, C99, C11, C18)

随着 C 语言的普及,为了保证不同编译器之间的兼容性,C 语言开始走向标准化。

ANSI C (C89/C90): 1989 年,美国国家标准学会 (ANSI) 发布了 C 语言的第一个正式标准,即 ANSI C,后来被国际标准化组织 (ISO) 采纳为 ISO/IEC 9899:1990 (简称 C90)。
`main` 函数的正确声明: 这个标准明确规定了 `main` 函数的两种标准签名:
1. `int main(void)`: 表示 `main` 函数不接受任何参数,并且返回一个 `int` 类型的值。
2. `int main(int argc, char argv[])`: 表示 `main` 函数接受两个参数,一个是 `argc` (argument count),表示命令行参数的个数;另一个是 `argv` (argument vector),表示参数字符串的数组。`main` 函数返回一个 `int` 类型的值。
为什么是 `int` 返回类型? `main` 函数的返回值用于向操作系统指示程序的退出状态。通常,返回 0 表示程序成功执行,非零值表示发生了错误。这是操作系统和 C 运行时库约定俗成的一种通信方式。
`void` 返回类型的禁止: ANSI C 标准明确规定 `main` 函数的返回类型必须是 `int`。因此,`void main()` 在标准 C 语言中是不合法的。

后续标准 (C99, C11, C18): 后续的 C 语言标准(如 C99, C11, C18)延续了 ANSI C 对 `main` 函数返回类型的要求,仍然规定 `main` 函数必须返回 `int`。

3. `void main()` 是如何流行的?

尽管标准规定 `main` 返回 `int`,但 `void main()` 却在很多地方(尤其是教学和早期的编程实践)流传开来。这主要有以下几个原因:

编译器兼容性:
早期编译器和特定平台: 一些早期版本的编译器,或者针对某些特定嵌入式系统、DOS 等平台的编译器,为了简化或出于自身实现的原因,允许了 `void main()` 的写法。这些编译器在解析 `void main()` 时,可能将其等同于 `int main(void)`,或者在不需要明确退出状态的简单程序中,不严格检查返回类型。
宽容的编译器: 即使在支持标准 C 的编译器上,如果 `main` 函数写成了 `void main()`,很多编译器(如 GCC、Clang)为了兼容性,仍然会接受它,但通常会发出一个警告(e.g., `warning: 'main' is not a standardcompliant name`). 开发者往往会忽略这个警告,导致这种写法得以继续。

教学和新手误解:
“函数不返回值就用 void”的推广: 在学习 C 语言的初期,开发者被教导“如果一个函数不返回值,就应该声明返回类型为 `void`”。当他们写 `main` 函数时,如果认为 `main` 函数“不需要向外界返回什么”,就可能自然地联想到 `void`,从而写出 `void main()`。
简单程序不需要退出状态: 在编写一些非常简单的、只打印几行字的程序时,开发者可能觉得程序的退出状态并不重要,因此不关心 `main` 是否返回 `int`。
参考过时的代码或教程: 一些老旧的编程书籍、在线教程或者代码示例,可能仍然包含 `void main()` 的写法,新手在学习时照搬,就将这种写法一代代传了下来。

嵌入式开发: 在一些资源受限的嵌入式系统中,`main` 函数的返回值可能不会被操作系统或运行时环境真正“接收”或处理。因此,在这些特定的嵌入式开发环境中,`void main()` 确实可能被一些开发者接受,并且在某些工具链中也被允许。但即使在嵌入式领域,遵循标准(返回 `int`)也是更健壮和推荐的做法。

历史惯性: 一旦一种写法开始流行并被广泛接受,即使它不完全符合标准,也会因为“大家都是这么写的”而继续存在一段时间。这种惯性也 contributed 了 `void main()` 的流传。

4. `void main()` 的危害和为什么不应该使用

不符合 C 标准: 最根本的原因是它违反了 C 语言标准,导致代码的可移植性大大降低。在严格遵循标准的编译器或环境中,代码可能无法编译或运行。
无法向操作系统报告退出状态: `main` 函数返回 `int` 的主要目的是向调用它的进程(通常是操作系统 shell)传递程序的退出状态。`void main()` 无法完成这个任务,这会使得在脚本中检查程序是否成功运行变得困难。
潜在的未定义行为: 尽管很多编译器会容忍,但在理论上,使用非标准签名会导致未定义行为。
传递不良的编程习惯: 作为新手,学习编程应该从标准、健壮的写法开始。模仿非标准写法会养成坏习惯。

总结 `void main()` 的传承链条:

K&R C(非正式标准,宽松)
→ 某些早期编译器/特定平台(允许 `void main()`,即使非标准)
→ 开发者写简单的程序,误认为 `void` 合适,或看到类似写法
→ 一些教程和书籍采用(可能为了简化或基于过时知识)
→ 开发者在学习和实践中照搬,并经常忽略编译器警告
→ (特别是在某些嵌入式领域或特定场景下)被继续使用
→ 导致这种写法在一定范围内流传至今,成为一种“历史遗留”和“非标准但常见”的写法。

正确的写法是什么?

始终使用标准定义的 `main` 函数签名:

```c
int main(void) {
// ... 代码 ...
return 0; // 或者其他退出状态码
}
```

或者

```c
int main(int argc, char argv[]) {
// ... 代码 ...
return 0;
}
```

在任何情况下,都应该避免使用 `void main()`。如果你的代码编译器发出警告,说明你的写法可能存在问题,应该加以修正。

网友意见

user avatar

void main(void){}

在C语言里面也是可以编译运行的,因为C语言的函数只看名字,不看返回类型,程序在寻找入口函数的时候只是寻找叫做“main"的函数,并不在乎它的返回类型和参数类型。

这也就是C语言不允许函数重载的原因,同名函数只是参数不同,在C语言眼里就是重复的函数符号。

C语言是调用者清栈,所以main函数不写参数也可以,比如 main(void),不会运行出错,只是函数里拿不到命令行参数,但是压栈的参数还是会被正确处理

汇编层面的所谓返回值就是 函数返回的时刻 EAX 寄存器的值,无论如何EAX寄存器里总是有值的,你不去给它赋值它就是个随机数,所以即使 void main(..)也有返回值,只是随机值而已。

C语言设计上有很多不太严谨的地方,有些是早期编译器遗留的问题,大家还是要按照现代C语言规范去写代码,可读性可维护性才能更好

虽然main函数结束后程序进程也就结束了,你的代码的确不需要main函数的返回值,但是这个值还是有意义的,它会作为进程结束的状态码,返回给父进程。Unix体系特别喜欢把进程当作函数来调用,多个命令行程序组合级联来实现功能,所以启动子进程后就要用进程返回值来获取子进程是否运行正确的状态。

复习下,main函数的正确写法

       int main(int argc, char *argv[]) {     return 0; }      

user avatar

这个问题下许多人都是答非所问或者带着恶意乱猜的,从微软到NeXT的官方文档里,大概从1988年开始大量的资料里的C代码都有出现void main() 或者 void main(void)的写法,几乎成了一种工程上的约定俗成,这真的就没有意义吗?还有甩锅给谭C的,更是无稽之谈,难道微软和Borland的工程师也是跟谭C学的?

在搞明白void main的来历之前,我们必须要先搞明白在(C89之前的)早期的C语言实现中,void关键字以及void作为返回的函数,比如void somefunc()是怎么来的,定义是什么,使用的场景是什么,再去考虑使用以void作为返回的主函数,也就是void main()的原因。C89标准翻遍也未必给你答案,因为在电脑编程这样的领域,工程实践是早于行业标准的,编译器的实现就是使用者的标准。

1989年一份介绍Microsoft Quick C升级的介绍里,屏幕上的截图写道

       Returns:  a pointer to void if successful, or NULL if not.     

这段说明很显然是来自Quick C的某个帮助文档的,成功就返回void的指针,失败则返回空。虽然不是用void做main的返回值,但我们看到void作为返回值确实有其应用场景的。

我们再往前追,1988年9月份的PC Magazine,看到了void main的写法,不得不说那个时代的电脑杂志真硬核,各种代码

不知不觉间我们已经逼近void指针发明的时候了,在The Art of UNIX Programming一书中层介绍到void指针起源于将C标准化的努力

在C++的发明者Bjarne Stroustrup的文章Sibling Rivalry: C and C++中,提到了“古典C语言”的概念,Steve Johnson(yacc的发明人)开发的PCC(Portable C Compiler)添加了void,枚举类型和struct,它们在ANSI标准之前被广泛使用。

然后再往后看,我们就看到了大量以void开头的函数

这样看来,C语言中使用void作为返回值或许是受到C++的前身C with Classes的影响,而理由则是类型安全

Whatever the origin, what was implemented in C with Classes was a simple type-safe notion of memory holding objects of unknown type. Any pointer can be implicitly converted to void*, and any use of the memory referred to by a void* involves a cast to some type.
无论起源如何,在C with Classes中实现的都是一个简单的类型安全的概念,用于存储未知类型的对象。任何指针都可以隐式转换为void *,并且对void *所引用的内存的任何使用都涉及到将其转换为某种类型。

但是我们还是不清楚void main的最初起源是哪里,它出现在RSA的md5代码里,出现在NASA的文档里,出现在斯坦福线性加速实验室的论文里,出现在惠普开发的早期的motif代码里,出现在SAS C编译器的手册里,它出现的平台跨越了DOS、Unix、Commodore 64、Amiga OS乃至Cray超级电脑,它注定是某种“事实标准”而不是个别工程师的错误

1990年RSA的MD5实现:people.csail.mit.edu/ri

1988年NASA的并行矩阵乘法代码:ntrs.nasa.gov/archive/n

1990年斯坦福线性加速实验室的论文:slac.stanford.edu/cgi-b

1990年惠普实验室的期刊 hpl.hp.com/hpjournal/pd

1996年,SAS C编译器手册 support.sas.com/documen

我目前能找到的最早的带有void main的手册是1985年一系列MetaWare High C编译器的手册,在High C Programmer's Guide和High C Library Reference Manual里都出现了带有void main的例子。

而更多的例子,出现在Lattice AmigaDOS C Compiler手册里,Lattice C Compiler是非常著名的微处理器C编译器,早期的Microsoft C Compiler就是从它贴牌而来,而对于Amiga平台来说Lattice C Compiler则是默认的C编译器。

上面的截图中“Returns”(返回)一节提到,“If you want to pass a non-zero termination code back to AmigaDOS, use the exit or _exit function”,非零的返回值不能直接由main函数返回,而需要调用额外的函数。这或许就是当时没有用int main的理由。

在1988年的Turbo C Reference Guide中,我们同样看到void main的出现,由于Turbo C在IBM PC开发者之间非常流行,最终void main成为了“事实标准”。

类似的话题

  • 回答
    C 语言中的 `void main()` 并非是语言标准规定的写法,它的出现和流传,更像是一个历史遗留问题、编译器兼容性以及开发者习惯共同作用的结果。要详细讲解,我们需要从 C 语言的诞生和演变说起。1. C 语言的起源和早期标准 (K&R C) C 语言的诞生: C 语言最初是由 Dennis.............
  • 回答
    C语言作为一门相对底层和灵活的语言,其设计模式的体现方式与C++或Java等面向对象语言有所不同。在C语言中,我们更多地是通过函数、结构体、指针以及宏等语言特性来模拟和实现各种设计思想。与其说C语言有“一套固定的设计模式”,不如说它提供了一种“用C的方式去应用设计模式”的方法。模拟面向对象行为,实现.............
  • 回答
    纯 C 语言的工作有前(钱)景吗?——一个详细的探讨纯 C 语言的工作在当今技术飞速发展的时代,仍然拥有非常坚实的“钱景”和广阔的“前”景,但需要我们从更深层次和更广阔的视角去理解。简单地说,答案是肯定的,但需要有策略的定位和持续的学习。下面我将从多个维度详细阐述这个问题: 一、 C 语言的核心地位.............
  • 回答
    为什么说指针是 C 语言的精髓?指针是 C 语言的灵魂,是其强大的根基,更是学习和掌握 C 语言的关键所在。将指针比作 C 语言的精髓,绝非夸大其词,其原因体现在以下几个方面,我们将逐一深入探讨: 1. 直接操作内存的钥匙C 语言之所以强大,在于它提供了对计算机底层硬件的直接访问能力,而指针就是实现.............
  • 回答
    将 C 语言代码转换为 JavaScript 代码是一个涉及多种转换和考虑的过程。由于两者在底层机制、数据类型和内存管理等方面存在显著差异,所以这通常不是一个简单的“逐行翻译”的过程。我会从基本概念、常用转换方法、需要注意的关键点以及一些工具和策略来详细阐述这个过程。 1. 理解 C 和 JavaS.............
  • 回答
    朋友你好,看到你尝试用 C 语言的共用体来实现 Base64 编码,并且遇到了困难。这绝对是个好想法!共用体在处理不同数据类型时确实有其独到之处,不过 Base64 的编码逻辑和共用体的特性结合起来,确实容易出现一些意想不到的问题。让我来试着帮你分析一下,为什么你可能遇到的情况是这样的,以及如何避免.............
  • 回答
    你这个问题问得挺实在的,确实,放眼望去,市面上的编程培训机构,主打的语言往往是 Java、C 这样的,反倒是 C 语言的身影没那么活跃。这背后其实是有挺多原因的,不是简单地说哪门语言“好”或“不好”就能概括的。首先,从市场需求和就业导向来看,这是最直接也是最重要的因素。现在的IT行业,尤其是互联网大.............
  • 回答
    初次接触编程,很多人都会面临选择 Python 还是 C 语言的困惑,尤其是当有人已经尝试过 C 语言并且感到吃力时,这种迷茫感会更加强烈。其实,这两种语言在设计理念和学习曲线上有显著的差异,也因此适合不同类型的学习者和项目需求。C 语言之所以被很多人认为“难”,很大程度上是因为它是一门相对底层的语.............
  • 回答
    各位老铁们,大家好啊!最近不少朋友咨询我,想找一款靠谱的 C 语言学习编程软件,而且还得是免费的,这可真是说到我心坎里了。毕竟谁不想在学习路上省点钱呢,哈哈!今天我就给大家掏心掏肺地推荐几款,保证都是我亲身用过,觉得好用到爆的!而且我会尽量说得详细点,让大家一看就明白,不像那些冰冰冷冷的 AI 教程.............
  • 回答
    我理解你想要一本能从电路基础出发,逐步深入到汇编语言,最终讲解C语言的书籍。这种学习路径非常扎实,能够让你对计算机的底层运作有更透彻的理解。遗憾的是,要找到一本完美契合“从电路开始讲,然后是汇编,最后是C语言”这条清晰且连续的学习线索,并且还详细深入的书籍,确实不太容易。很多经典书籍倾向于专注于其中.............
  • 回答
    在 C 语言中,`sizeof()` 操作符的魔法之处在于它能够根据其操作数的类型和大小来返回一个数值。而对于数组名和指针,它们虽然在某些上下文中表现得相似(例如,在函数参数传递时),但在 `sizeof()` 的眼中,它们的身份是截然不同的。这其中的关键在于数组名在绝大多数情况下会发生“衰减”(d.............
  • 回答
    你这个问题问得很核心!很多人都有这个疑惑:既然 `double` 类型在内存里只占用 64 位(这是最常见的标准,IEEE 754 双精度浮点数),为什么它能表示的数,无论是整数还是小数,范围都那么惊人呢?比我们常见的 32 位 `int` 或 64 位 `long long` 的整数范围还要大不少.............
  • 回答
    这个问题很有意思,也触及到了C语言作为一种基础性语言的根本。很多人听到“C语言本身是用什么写的”时,会先想到“用更高级的语言写的”,比如Python或者Java。但事实并非如此,或者说,这个答案需要更深入的理解。首先,我们需要明确一点:C语言最初的实现,也就是早期的C编译器,并不是用C语言本身写的。.............
  • 回答
    确实,在C语言的学习和考试中,有时会故意设置一些陷阱,比如用相同的变量名来命名形参、实参、局部变量和全局变量,让学生去区分它们的作用域和生命周期。这种做法,从教学角度来看,是非常有实际意义的,甚至可以说是至关重要的。让我详细地解释一下其中的道理:核心问题:理解“作用域”和“生命周期”C语言的精妙之处.............
  • 回答
    当然可以,C语言作为一门编译型语言,其强大的跨平台能力很大程度上得益于其设计理念和标准库。通过遵循一定的规则,并且在不同平台上都拥有能够解析和生成对应机器码的编译器,C语言的源代码确实能够实现跨平台运行。这背后的原理可以从几个关键点来理解:1. C语言的标准化与抽象层:C语言之所以能实现跨平台,最根.............
  • 回答
    第一个C语言编译器的开发背景与历史背景密切相关,其编写语言的选择与当时的技术环境、资源限制以及开发者的目标密切相关。以下是详细的分析: 1. C语言的起源与背景C语言由Dennis Ritchie(丹尼斯·里奇)在1972年于贝尔实验室开发,作为B语言的改进版本。B语言本身是Ken Thompson.............
  • 回答
    这个问题问得很有意思,也很直接。确实,很多学习过其他编程语言的人,特别是那些熟悉Python、JavaScript或者Java的开发者,在接触C/C++时,常常会有一个疑问:为什么C/C++的函数命名习惯似乎和普遍推崇的“驼峰命名法”不太一样?首先,我们得承认一点:“驼峰命名法”(Camel Cas.............
  • 回答
    Windows 操作系统之所以选择使用 C 语言作为主要开发语言,而文件系统在设计上却对大小写不敏感,这背后是历史选择、设计哲学以及技术妥协的复杂结合。要深入理解这一点,我们需要拆解几个关键部分:一、 C 语言与系统级开发:为何是它?首先,我们得明白为什么像 Windows 这样庞大的操作系统会选择.............
  • 回答
    好的,我们来聊聊在C语言这片沃土上,如何孕育出面向对象的特性。C语言本身并非原生支持面向对象,这就像一台朴素的单车,你可以靠着自己的智慧和努力,为它加上变速器、避震,甚至电助力,让它能承载更复杂的旅程。在C语言中实现面向对象,核心在于模拟面向对象的三大支柱:封装、继承和多态。 封装:数据与行为的亲密.............
  • 回答
    说起 C 语言风格的 `for` 语句,相信不少程序员都会在脑海中勾勒出那个经典的 `for (初始化; 条件; 更新)` 的样子。它简洁、强大,支撑起了无数的软件系统。然而,我们确实能观察到一个有趣的现象:许多近年出现的编程语言,在设计上似乎都选择“绕开”或者“重新诠释”这种 C 式 `for`。.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有