写了多年程序,你真的懂main函数吗?
程序本质是定义可重复流程的函数;在操作系统内运行的程序有四个基本要素。
程序是一种有副作用的函数
1. 什么是程序
我们先来看一个C语言的main函数:

它包含了一个程序的基本特征:它是一个函数。他不仅是个函数,还对外界做了一个操作,打印 Hello World。这就跟数学函数不一样了。想想以前学过的三角函数,y=sin(x),输入 x,输出 y,y 与 x 的值有关,运算的时候不会做对外界有影响的其他事情。
这个 main 函数就不一样,给屏幕上打印了一行字。假设 main 函数很复杂,但运算结果你看不到,这有什么用呢?所以,对外界的操作是一个程序的基本特征,用专业的话来说,就是一个有副作用的函数。
比如我们人,最重要的事就是与外界交互。每个人根据天赋和后天教育不同,会来到各个岗位做不同的事情。各类程序,也必须与所处的环境产生交互,并与其他程序协同完成任务。与人直接交互的程序,如浏览器、办公软件等,能给人提高办公效率,帮助人完成很多工作,所以人们就会重视。在幕后负责基础工作的程序,如操作系统、数据库,用户平时关注不到,但它们是其他软件运行的基础,所以也不可或缺。基础程序的能力越强,越能带动多数程序提升整体质量。一组分工不同的程序,共同组成了目前我们使用的操作系统界面。
我们编写程序的核心目的,是为了定义一个流程,让这个流程可高效重复。就像开一家店、开一条生产线,必须确定一种完善的流程,才能生产一种产品和服务。这个流程在外部看来,就是一个 main 函数,其中用各种手段,会给系统环境造成各种影响,写了文件,收发了数据。运行这个 main 函数,就是执行一遍这套流程,这才达到编写程序的目的。
所以,衡量一件事值不值得写个程序去做,或者大一点,建一套信息化系统,就要首先考虑这件事其中可重复的部分有哪些,每次做有多少相似之处,如何合并相似之处,提升质量和效率。包100个馄饨,自己凭经验包问题不大;要包1000个,就要固定配方了;要包上万个,就要固定工艺流程了。处理10个数据,手动修改;处理1000个,要考虑用公式、写代码了。
上面举例用的是C语言,在其他语言中,与C语言的 main 函数形式不一样,或者没有 main 函数,最后给到操作系统的接口,都会统一成 main 函数的形式,这样无论用什么语言编程,都可以在同一个系统上运行。
2. 程序的基本要素

在操作系统上运行的程序主要有几个要素:命令行参数,环境变量,返回值,输入输出。
命令行参数是 main 函数的参数,是一个字符串列表。一般我们见到命令行参数主要用于给定输入输出文件地址,调整程序功能。例如:python3 a.py input_file ,对于 python3 来说,会收到三个命令行参数,第一个参数是调用时的程序名(python3),后面是 a.py 和 input_file。
环境变量,是以名称=值的形式,给所有程序的默认配置,通常是给程序提供系统、用户、语言信息和一些默认路径。例如有些人可能会记得安装某些软件配PATH,这个事情的目的,就是让系统运行程序时,从PATH列出的路径中找出调用的同名程序的确切位置。TMPDIR/TEMP 环境变量可以指定临时文件保存位置,避免系统盘空间不足。
返回值是一个整数,代表程序怎么结束的。0 代表正常退出,其他值代表各种异常情况。较小的数(<128)是程序自己定义的,其他是系统定义的。一般只有调用这个程序的上级程序会关心,所以写代码时不会特地定义返回值,异常退出都会不等于0。
输入输出是程序与外界交互的主要方式,体现为Windows中的句柄(handle),Linux中的文件描述符(fd)。
| Linux 文件描述符(fd) | Windows 句柄(Handle) | 说明 |
|---|---|---|
| 标准输入/输出/错误(0,1,2) | GetStdHandle(STD_*_HANDLE) |
通过 STD_INPUT_HANDLE、STD_OUTPUT_HANDLE 等获取标准流。 |
| 普通文件 | CreateFile() 返回的 HANDLE |
用于文件读写(需指定权限如 GENERIC_READ)。 |
| 匿名管道 | CreatePipe() 返回的读写句柄 |
类似 Linux 的 pipe(),但句柄是内核对象。 |
| 命名管道 | CreateNamedPipe() 或 CreateFile |
通过名称访问的管道(类似 FIFO)。 |
| 套接字(Socket) | WSASocket() 返回的 SOCKET |
Windows 套接字 API 独立于句柄系统,但可通过 WSADuplicateSocket 共享。 |
| 设备文件 | CreateFile() 打开设备路径 |
如 \\.\COM1(串口)、\\.\NUL(等效于 /dev/null)。 |
| 事件/信号 | CreateEvent() 或 CreateSemaphore |
Windows 使用内核对象(Event、Mutex 等)管理同步,句柄需通过 WaitForSingleObject 等待。 |
| 目录操作 | FindFirstFile() 返回的 HANDLE |
遍历目录内容(不同于 Linux 的 opendir/readdir)。 |
操作系统会给所有程序自带三个输入输出流:stdin 标准输入,stdout 标准输出,stderr 标准错误。stdin 通常是从控制台屏幕或管道读取的输入,stdout 是 print 函数打印到屏幕上的那个输出管道,stderr 通常用来打印日志和错误信息。
如果从终端运行程序,这三个自带输入输出都连接到了终端屏幕上,键盘输入的进入了 stdin,程序打印的输出显示在了终端屏幕上。在IDEA, PyCharm等IDE中,stdout 打的字是正常黑/白色,stderr 打的字是红色。如果运行的是图形界面程序,这三个流就失去了作用。
在 Linux/Windows 的命令行(bash/cmd)脚本中,| 是将前一个命令的 stdout 连接到后一个命令的 stdin,实现流式处理数据;> 是将命令的 stdout 改为向文件输出,不再显示在屏幕上。
程序运行过程中,通常会打开文件、套接字(Socket),用于读写文件、连接网络。这些操作返回的对象在系统层面与上述三个标准流平级,都是用于输入输出的对象。
上述这些要素中,命令行参数和环境变量是一个程序运行前的输入;返回值是程序退出的指示,可能影响其他程序的行为;输入输出是程序是程序与外界交互实现其功能的主要途径。
3. 总结
程序的本质是定义可重复执行的流程,并通过与外界交互(即“副作用”)实现价值。无论是C语言的main函数,还是其他语言的入口逻辑,最终都需要通过命令行参数、环境变量、返回值、输入输出(I/O) 这四个核心要素与系统、用户、其他程序协作。这些要素是程序的“接口”:命令行参数与环境变量是“输入配置”,返回值是“退出状态反馈”,I/O是“数据交互通道”。理解它们,就抓住了程序运行的底层逻辑。
很多人初期容易陷入“实现功能就好”的误区,却忽略程序是活在系统中的。比如:
- 写脚本时,硬编码文件路径(如
/data/file.txt)不如通过命令行参数传入(--input /data/file.txt),后者更灵活适配不同场景; - 部署服务时,把端口、日志路径写死在代码里,不如用环境变量(
export PORT=8080)让运维人员快速调整; - 处理异常时,只打印“出错了”而不设置非0返回值(如
exit(1)),会导致调用脚本无法判断程序是否执行成功。
作为软件工程师,别只关注代码逻辑,更要重视程序与环境的交互 。
写代码前先问自己:“这个程序会被谁调用?需要接收什么配置?如何告诉调用者‘我成功了/失败了’?数据从哪来、到哪去?” 把这些问题的答案融入逻辑设计,程序才真正“可用”。
更多推荐



所有评论(0)