蚊章汇 - Erik O'shaughnessy
type: channel
link: https://linuxcn.bwsl.wang/author/1245/
description: Erik O'shaughnessy
items: 用 Python 解析命令行参数
items: 使用 Python 在 GitHub 上运行你的博客
items: 如何组织构建多文件 C 语言程序（二）
items: 如何组织构建多文件 C 语言程序（一）
items: 12 个给全等级码农们的编程资源
items: 浅谈汇编器、编译器和解释器
items: 如何写好 C main 函数

用 Python 解析命令行参数
type: item
date: 2020-06-06T07:16:19+08:00
description: 借鉴 C 语言的历史，学习如何用 Python 编写有用的 CLI 程序。本文的目标很简单：帮助新的 Python 开发者了解一些关于命令行接口（CLI）的历史和术语，并探讨如何在 Python 中编写这些有用的程序。最初……首先，从 Unix 的角度谈谈命令行界面设计。Unix 是一种计算机操作系统，也是 Linux 和 macOS（以及许多其他操作系统）的祖先。在图形用户界面之前，用户通过命令行提示符与计算机进行交互（想想如今的 Bash 环境）。在 Unix 下开发这些程序的主要语言是 C)，它的功能非常强大。因此，我们至少应该了解 C 程序的基础知识。假设你没有读过上面那个链接的内容，C 程序的基本架构是一个叫做 main 的函数，它的签名是这样的。 int main(int argc, char **argv) { ... }对于 Python 程序员来说，这应该不会显得太奇怪。C 函数首先有一个返回类型、一个函数名，然后是括号内的类型化参数。最后，函数的主体位于大括号之间。函数名 main 是运行时链接器（构造和运行程序的程序）如何决定从哪里开始执行你的程序。如果你写了一个 C 程序，而它没有包含一个名为 main 的函数，它将什么也做不了。伤心。函数参数变量 argc 和 argv 共同描述了程序被调用时用户在命令行输入的字符串列表。在典型的 Unix 命名传统中，argc 的意思是“ 参数计数 argument count ”，argv 的意思是“ 参数向量 argument vector ”。向量听起来比列表更酷，而 argl 听起来就像一个要勒死的求救声。我们是 Unix 系统的程序员，我们不求救。我们让其他人哭着求救。再进一步$ ./myprog foo bar -x baz如果 myprog 是用 C 语言实现的，则 argc 的值是 5，而 argv 是一个有五个条目的字符指针数组。（不要担心，如果这听起来过于技术，那换句话说，这是一个由五个字符串组成的列表。）向量中的第一个条目 argv[0] 是程序的名称。argv 的其余部分包含参数。 argv[0] == "./myprog" argv[1] == "foo" argv[2] == "bar" argv[3] == "-x" argv[4] == "baz" /* 注：不是有效的 C 代码 */在 C 语言中，你有很多方法来处理 argv 中的字符串。你可以手动地循环处理数组 argv，并根据程序的需要解释每个字符串。这相对来说比较简单，但会导致程序的接口大相径庭，因为不同的程序员对什么是“好”有不同的想法。include <stdio.h> /* 一个打印 argv 内容的简单 C 程序。 */ int main(int argc, char **argv) { int i; for(i=0; i<argc; i++) printf("%s\n", argv[i]); }早期对命令行标准化的尝试命令行武器库中的下一个武器是一个叫做 getopt 的 C 标准库函数。这个函数允许程序员解析开关，即前面带破折号的参数（比如 -x），并且可以选择将后续参数与它们的开关配对。想想 /bin/ls -alSh 这样的命令调用，getopt 就是最初用来解析该参数串的函数。使用 getopt 使命令行的解析变得相当简单，并改善了用户体验（UX）。include <stdio.h> #include <getopt.h> #define OPTSTR "b:f:" extern char *optarg; int main(int argc, char **argv) { int opt; char *bar = NULL; char *foo = NULL; while((opt=getopt(argc, argv, OPTSTR)) != EOF) switch(opt) { case 'b': bar = optarg; break; case 'f': foo = optarg; break; case 'h': default': fprintf(stderr, "Huh? try again."); exit(-1); /* NOTREACHED */ } printf("%s\n", foo ? foo : "Empty foo"); printf("%s\n", bar ? bar : "Empty bar"); }就个人而言，我希望 Python 有开关，但这永远、永远不会发生。GNU 时代GNU 项目出现了，并为他们实现的传统 Unix 命令行工具引入了更长的格式参数，比如--file-format foo。当然，我们这些 Unix 程…

使用 Python 在 GitHub 上运行你的博客
type: item
date: 2020-04-02T18:10:48+08:00
description: 使用 Pelican 创建博客，这是一个基于 Python 的平台，与 GitHub 配合的不错。GitHub 是一个非常流行的用于源代码控制的 Web 服务，它使用 Git 同步本地文件和 GitHub 服务器上保留的副本，这样你就可以轻松地共享和备份你的工作。除了为代码仓库提供用户界面之外，GitHub 还运允许用户直接从仓库发布网页。GitHub 推荐的网站生成软件包是 Jekll，是使用 Ruby 编写的。因为我是 Python 的忠实粉丝，所以我更喜欢 Pelican，这是一个基于 Python 的博客平台，可与 GitHub 很好地协同工作。Pelican 和 Jekll 都可以将 Markdown 或 reStructuredText 中编写的内容转换为 HTML 以生成静态网站，并且两个生成器都支持定制的主题。在本文中，我将介绍如何安装 Pelican、设置 GitHub 仓库、运行快速入门帮助、编写一些 Markdown 文件以及发布第一篇博客。我假设你有一个 GitHub 账户，熟悉基础的 Git 命令，并且想使用 Pelican 发布博客。安装 Pelican 并创建仓库首先，你必须在本地计算机上安装 Pelican 和 ghp-import。使用 Python 软件包安装工具 pip（你有，对吧？），这非常容易：$ pip install pelican ghp-import Markdown然后，打开浏览器并在 GitHub 上为你新鲜出炉的博客创建一个新仓库，命名如下（在此处以及整个教程中，用 GitHub 用户名替换 username）：https://GitHub.com/username/username.github.io让它保持为空，稍后我们用引人注目的博客内容来填充它。使用命令行（确保正确），将这个空 Git 仓库克隆到本地计算机：$ git clone <https://GitHub.com/username/username.github.io> blog $ cd blog奇怪的把戏…在 GitHub 上发布 Web 内容有一个不太引入注意的技巧，对于托管在名为 username.github.io 的仓库的用户页面，其内容由 master 分支提供服务。我强烈建议所有的 Pelican 配置文件和原始的 Markdown 文件都不要保留在 master 中，master 中只保留 Web 内容。因此，我将 Pelican 配置和原始内容保留在一个我喜欢称为 content 的单独分支中。（你可以随意创建一个分支，但以下内容沿用 content。）我喜欢这种结构，因为我可以放弃掉 master 中的所有文件，然后用 content 分支重新填充它。$ git checkout -b content Switched to a new branch 'content'配置 Pelican现在该进行内容配置了。Pelican 提供了一个很棒的初始化工具 pelican-quickstart，它会询问你有关博客的一系列问题。$ pelican-quickstart Welcome to pelican-quickstart v3.7.1. This script will help you create a new Pelican-based website. Please answer the following questions so this script can generate the files needed by Pelican. > Where do you want to create your new web site? [.] > What will be the title of this web site? Super blog > Who will be the author of this web site? username > What will be the default language of this web site? [en] > Do you want to specify a URL prefix? e.g., http://example.com (Y/n) n > Do you want to enable article pagination? (Y/n) > How many articles per page do you want? [10] > What is your time zone? [Europe/Paris] US/Central > Do you want to generate a Fabfile/Make…

如何组织构建多文件 C 语言程序（二）
type: item
date: 2020-03-16T20:30:00+08:00
description: 我将在本系列的第二篇中深入研究由多个文件组成的 C 程序的结构。在第一篇中，我设计了一个名为喵呜喵呜的多文件 C 程序，该程序实现了一个玩具编解码器。我也提到了程序设计中的 Unix 哲学，即在一开始创建多个空文件，并建立一个好的结构。最后，我创建了一个 Makefile 文件夹并阐述了它的作用。在本文中将另一个方向展开：现在我将介绍简单但具有指导性的喵呜喵呜编解码器的实现。当读过我的《如何写一个好的 C 语言 main 函数》后，你会觉得喵呜喵呜编解码器的 main.c 文件的结构很熟悉，其主体结构如下：/* main.c - 喵呜喵呜流式编解码器 */ /* 00 系统包含文件 */ /* 01 项目包含文件 */ /* 02 外部声明 */ /* 03 定义 */ /* 04 类型定义 */ /* 05 全局变量声明（不要用）*/ /* 06 附加的函数原型 */ int main(int argc, char *argv[]) { /* 07 变量声明 */ /* 08 检查 argv[0] 以查看该程序是被如何调用的 */ /* 09 处理来自用户的命令行选项 */ /* 10 做点有用的事情 */ } /* 11 其它辅助函数 */包含项目头文件位于第二部分中的 /* 01 项目包含文件 */ 的源代码如下：/* main.c - 喵呜喵呜流式编解码器 */ ... /* 01 项目包含文件 */ #include "main.h" #include "mmecode.h" #include "mmdecode.h"#include 是 C 语言的预处理命令，它会将该文件名的文件内容拷贝到当前文件中。如果程序员在头文件名称周围使用双引号（""），编译器将会在当前目录寻找该文件。如果文件被尖括号包围（<>），编译器将在一组预定义的目录中查找该文件。main.h 文件中包含了 main.c 文件中用到的定义和类型定义。我喜欢尽可能多将声明放在头文件里，以便我在我的程序的其他位置使用这些定义。头文件 mmencode.h 和 mmdecode.h 几乎相同，因此我以 mmencode.h 为例来分析。/* mmencode.h - 喵呜喵呜流编解码器 */ #ifndef _MMENCODE_H #define _MMENCODE_H #include <stdio.h> int mm_encode(FILE *src, FILE *dst); #endif /* _MMENCODE_H */#ifdef、#define、#endif 指令统称为 “防护” 指令。其可以防止 C 编译器在一个文件中多次包含同一文件。如果编译器在一个文件中发现多个定义/原型/声明，它将会产生警告。因此这些防护措施是必要的。在这些防护内部，只有两个东西：#include 指令和函数原型声明。我在这里包含了 stdio.h 头文件，以便于能在函数原型中使用 FILE 定义。函数原型也可以被包含在其他 C 文件中，以便于在文件的命名空间中创建它。你可以将每个文件视为一个独立的命名空间，其中的变量和函数不能被另一个文件中的函数或者变量使用。编写头文件很复杂，并且在大型项目中很难管理它。不要忘记使用防护。喵呜喵呜编码的最终实现该程序的功能是按照字节进行 MeowMeow 字符串的编解码，事实上这是该项目中最简单的部分。截止目前我所做的工作便是支持允许在适当的位置调用此函数：解析命令行，确定要使用的操作，并打开将要操作的文件。下面的循环是编码的过程：/* mmencode.c - 喵呜喵呜流式编解码器 */ ... while (!feof(src)) { if (!fgets(buf, sizeof(buf), src)) break; for(i=0; i<strlen(buf); i++) { lo = (buf[i] & 0x000f); hi = (buf[i] & 0x00f0) >> 4; fputs(tbl[hi], dst); fputs(tbl[lo], dst); } }简单的说，当文件中还有数据块时（ feof(3) ），该循环读取（feof(3) ）文件中的一个数据块。然后将读入的内容的每个字节分成两个 hi 和 lo 的 半字节 nibble 。半字节是半个字节，即 4 个位。这里的奥妙之处在于可以用 4 个位来编码 16 个值。我将 hi 和 lo 用作 16 个字符串查找表 tbl 的索引，表中包含了用半字节编码的 MeowMeow 字符串。这些字符串使用 fputs(3) 函数写入目标 FILE 流，然后我们继续处理缓存区的下一个字节。该表使用 table.h 中的宏定义进行初始化，在没有特殊原因（比如：要展示包含了另…

如何组织构建多文件 C 语言程序（一）
type: item
date: 2020-02-27T05:45:34+08:00
description: 准备好你喜欢的饮料、编辑器和编译器，放一些音乐，然后开始构建一个由多个文件组成的 C 语言程序。大家常说计算机编程的艺术部分是处理复杂性，部分是命名某些事物。此外，我认为“有时需要添加绘图”是在很大程度上是正确的。在这篇文章里，我会编写一个小型 C 程序，命名一些东西，同时处理一些复杂性。该程序的结构大致基于我在 《如何写一个好的 C 语言 main 函数》 文中讨论的。但是，这次做一些不同的事。准备好你喜欢的饮料、编辑器和编译器，放一些音乐，让我们一起编写一个有趣的 C 语言程序。优秀 Unix 程序哲学首先，你要知道这个 C 程序是一个 Unix 命令行工具。这意味着它运行在（或者可被移植到）那些提供 Unix C 运行环境的操作系统中。当贝尔实验室发明 Unix 后，它从一开始便充满了设计哲学。用我自己的话来说就是：程序只做一件事，并做好它，并且对文件进行一些操作。虽然“只做一件事，并做好它”是有意义的，但是“对文件进行一些操作”的部分似乎有点儿不合适。事实证明，Unix 中抽象的 “文件” 非常强大。一个 Unix 文件是以文件结束符（EOF）标志为结尾的字节流。仅此而已。文件中任何其它结构均由应用程序所施加而非操作系统。操作系统提供了系统调用，使得程序能够对文件执行一套标准的操作：打开、读取、写入、寻址和关闭（还有其他，但说起来那就复杂了）。对于文件的标准化访问使得不同的程序共用相同的抽象，而且可以一同工作，即使它们是不同的人用不同语言编写的程序。具有共享的文件接口使得构建可组合的的程序成为可能。一个程序的输出可以作为另一个程序的输入。Unix 家族的操作系统默认在执行程序时提供了三个文件：标准输入（stdin）、标准输出（stdout）和标准错误（stderr）。其中两个文件是只写的：stdout 和 stderr。而 stdin 是只读的。当我们在常见的 Shell 比如 Bash 中使用文件重定向时，可以看到其效果。$ ls | grep foo | sed -e 's/bar/baz/g' > ack这条指令可以被简要地描述为：ls 的结果被写入标准输出，它重定向到 grep 的标准输入，grep 的标准输出重定向到 sed 的标准输入，sed 的标准输出重定向到当前目录下文件名为 ack 的文件中。我们希望我们的程序在这个灵活又出色的生态系统中运作良好，因此让我们编写一个可以读写文件的程序。喵呜喵呜：流编码器/解码器概念当我还是一个露着豁牙的孩子懵懵懂懂地学习计算机科学时，学过很多编码方案。它们中的有些用于压缩文件，有些用于打包文件，另一些毫无用处因此显得十分愚蠢。列举最后这种情况的一个例子：哞哞编码方案。为了让我们的程序有个用途，我为它更新了一个 21 世纪 的概念，并且实现了一个名为“喵呜喵呜” 的编码方案的概念（毕竟网上大家都喜欢猫）。这里的基本的思路是获取文件并且使用文本 “meow” 对每个半字节（半个字节）进行编码。小写字母代表 0，大写字母代表 1。因为它会将 4 个比特替换为 32 个比特，因此会扩大文件的大小。没错，这毫无意义。但是想象一下人们看到经过这样编码后的惊讶表情。$ cat /home/your_sibling/.super_secret_journal_of_my_innermost_thoughts MeOWmeOWmeowMEoW...这非常棒。最终的实现完整的源代码可以在 GitHub 上面找到，但是我会写下我在编写程序时的思考。目的是说明如何组织构建多文件 C 语言程序。既然已经确定了要编写一个编码和解码“喵呜喵呜”格式的文件的程序时，我在 Shell 中执行了以下的命令 ：$ mkdir meowmeow $ cd meowmeow $ git init $ touch Makefile # 编译程序的方法 $ touch main.c # 处理命令行选项 $ touch main.h # “全局”常量和定义 $ touch mmencode.c # 实现对喵呜喵呜文件的编码 $ touch mmencode.h # 描述编码 API $ touch mmdecode.c # 实现对喵呜喵呜文件的解码 $ touch mmdecode.h # 描述解码 API $ touch table.h # 定义编码查找表 $ touch .gitignore # 这个文件中的文件名会被 git 忽略 $ git add . $ git commit -m "initial commit of empty files"简单的说，我创建了一个目录，里面全是空文件，并且提交到 git。即使这些文件中没有内容，你依旧可以从它的文件名推断每个文件的用途。为了避免万一你无法理解，我在每条 …

12 个给全等级码农们的编程资源
type: item
date: 2020-01-05T18:20:40+08:00
description: 无论你身处编程旅程中的何处，这 12 篇编程文章都有你需要学习一些东西。“学习计算机编程的最佳时间是 20 年前，其次是现在。” — 计算机科学的谚语（也许是）无论你是新程序员、经验丰富的 Web 开发人员、后端大师、头发花白的系统程序员，还是其他神秘物种的计算机极客？无论你身处广阔的软件开发人员生态系统中的何处，在我们领域中唯一不变的就是需要了解新技术及其应用方法。这是过去一年来阅读量最大的 Opensource.com 文章的集合，我很高兴在这里与你分享它们。好好学习无论你的技能或兴趣是什么，学习如何编写计算机程序都可以将特定领域的知识变成一种超能力。不幸的是，至少从现在开始，还不可能通过坐在计算机前面接受电磁辐射就能学会如何编程。在这之前，我推荐这些出色的文章来帮助激发你的编程技能。它们解决了一些重要的问题，例如考虑学习哪种语言以及不同的入门方式。你应该学习哪种编程语言？（译文）学习 Python 的 12 个方式（译文）你可以使用树莓派学习的三种流行编程语言（译文）利用 Python 引导孩子的计算机思维（译文）天天向上经验丰富的程序员知道，与学习编程相比，唯一更难的就是使你的技能和知识保持最新。但是我们是自学成才的一族，总是希望提高我们的专业知识和理解力。即使我们不期望使用新技术，我们也知道总会有人问起它们。这些有关 Rust、WebAssembly 和 Podman 的文章是开始学习软件技术一些即将出现的趋势的好地方。为什么要在 WebAssembly 中使用 Rust？使用 WebAssembly 扩展命令行领域免 root 的 Podman 如何工作？为什么选择 Rust 作为你的下一种编程语言老兵不死尽管新技术层出不穷，但回顾过去可以帮助你前进。当今惊人的技术是建立在昨天的工具之上的，即使我们从未使用这些传奇语言编写过代码，它也将使我们很好地理解这些工具的功能和局限性。这一系列文章集中于 C 语言、用 AWK 编写的实际应用程序，以及对流行度下降但也许还没有消亡的计算机语言的有趣讨论。C 的巨大影响如何写好 C main 函数（译文）用 AWK 喝咖啡（译文）你最喜欢的“死”语言是什么？学习不止无论你在编程过程中处于何处，总有更多东西需要学习。我希望这些来自 2019 年的顶级资源能帮助你为 2020 年做好准备。感谢作者们和你——读者。via: https://opensource.com/article/19/12/programming-resources作者：Erik O'Shaughnessy 选题：lujun9972 译者：wxy 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

浅谈汇编器、编译器和解释器
type: item
date: 2019-06-27T02:13:00+08:00
description: 简单介绍一下编程方式的历史演变。在计算机诞生不久的早期年代，硬件非常昂贵，而程序员比较廉价。这些廉价程序员甚至都没有“程序员”这个头衔，并且常常是由数学家或者电气工程师来充当这个角色的。早期的计算机被用来快速解决复杂的数学问题，所以数学家天然就适合“编程”工作。什么是程序？首先来看一点背景知识。计算机自己是做不了任何事情的，它们的任何行为都需要程序来引导。你可以把程序看成是非常精确的菜谱，这种菜谱读取一个输入，然后生成对应的输出。菜谱里的各个步骤由操作数据的指令构成。听上去有点儿复杂，不过你或许知道下面这个语句是什么意思：1 + 2 = 3其中的加号是“指令”，而数字 1 和 2 是数据。数学上的等号意味着等式两边的部分是“等价”的，不过在大部分编程语言中对变量使用等号是“赋值”的意思。如果计算机执行上面这个语句，它会把这个加法的结果（也就是“3”）储存在内存中的某个地方。计算机知道如何使用数字进行数学运算，以及如何在内存结构中移动数据。在这里就不对内存进行展开了，你只需要知道内存一般分为两大类：“速度快/空间小”和“速度慢/空间大”。CPU 寄存器的读写速度非常快，但是空间非常小，相当于一个速记便签。主存储器通常有很大的空间，但是读写速度就比寄存器差远了。在程序运行的时候，CPU 不断将它所需要用到的数据从主存储器挪动到寄存器，然后再把结果放回到主存储器。汇编器当时的计算机很贵，而人力比较便宜。程序员需要耗费很多时间把手写的数学表达式翻译成计算机可以执行的指令。最初的计算机只有非常糟糕的用户界面，有些甚至只有前面板上的拨动开关。这些开关就代表一个内存“单元”里的一个个 “0” 和 “1”。程序员需要配置一个内存单元，选择好储存位置，然后把这个单元提交到内存里。这是一个既耗时又容易出错的过程。程序员Betty Jean Jennings (左) 和 Fran Bilas (右) 在操作 ENIAC 的主控制面板后来有一名 电气工程师 认为自己的时间很宝贵，就写了一个程序，能够把人们可以读懂的“菜谱”一样的输入转换成计算机可以读懂的版本。这就是最初的“汇编器”，在当时引起了不小的争议。这些昂贵机器的主人不希望把计算资源浪费在人们已经能做的任务上（虽然又慢又容易出错）。不过随着时间的推移，人们逐渐发现使用汇编器在速度和准确性上都胜于人工编写机器语言，并且计算机完成的“实际工作量”增加了。尽管汇编器相比在机器面板上切换比特的状态已经是很大的进步了，这种编程方式仍然非常专业。上面加法的例子在汇编语言中看起来差不多是这样的：01 MOV R0, 1 02 MOV R1, 2 03 ADD R0, R1, R2 04 MOV 64, R0 05 STO R2, R0每一行都是一个计算机指令，前面是一个指令的简写，后面是指令所操作的数据。这个小小的程序首先会将数值 1 “移动”到寄存器 R0，然后把 2 移动到寄存器 R1。03 行把 R0 和 R1 两个寄存器里的数值相加，然后将结果储存在 R2 寄存器里。最后，04 行和 05 行决定结果应该被放在主存储器里的什么位置（在这里是地址 64）。管理内存中存储数据的位置是编程过程中最耗时也最容易出错的部分之一。编译器汇编器已经比手写计算机指令要好太多了，不过早期的程序员还是渴望能够按照他们所习惯的方式，像书写数学公式一样地去写程序。这种需求推动了高级编译语言的发展，其中有一些已经成为历史，另一些如今还在使用。比如 ALGO 就已经成为历史了，但是像 Fortran 和 C) 这样的语言仍然在不断解决实际问题。ALGO 和 Fortran 编程语言的谱系树这些“高级”语言使得程序员可以用更简单的方式编写程序。在 C 语言中，我们的加法程序就变成了这样：int x; x = 1 + 2;第一个语句描述了该程序将要使用的一块内存。在这个例子中，这块内存应该占一个整数的大小，名字是 x。第二个语句是加法，虽然是倒着写的。一个 C 语言的程序员会说这是 “X 被赋值为 1 加 2 的结果”。需要注意的是，程序员并不需要决定在内存的什么位置储存 x，这个任务交给编译器了。这种被称为“编译器”的新程序可以把用高级语言写的程序转换成汇编语言，再使用汇编器把汇编语言转换成机器可读的程序。这种程序组合常常被称为“工具链”，因为一个程序的输出就直接成为另一个程序的输入。编译语言相比汇编语言的优势体现在从一台计算机迁移到不同型号或者品牌的另一台计算机上的时候。在计算机的早期岁月里，包括 IBM、DEC、德州仪器、UNIVAC 以及惠普在内的很多公司都在制造除了大量不同类型的计算机硬件。这些计算机除了都需要连接电源之外就没有太多共同点了。它们在内存和 CPU 架构上的差异相当大，当时经常需要人们花费数年来将一台计算机…

如何写好 C main 函数
type: item
date: 2019-06-09T05:15:00+08:00
description: 学习如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。我知道，现在孩子们用 Python 和 JavaScript 编写他们的疯狂“应用程序”。但是不要这么快就否定 C 语言 —— 它能够提供很多东西，并且简洁。如果你需要速度，用 C 语言编写可能就是你的答案。如果你正在寻找稳定的职业或者想学习如何捕获空指针解引用，C 语言也可能是你的答案！在本文中，我将解释如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。我：一个顽固的 Unix 系统程序员。你：一个有编辑器、C 编译器，并有时间打发的人。让我们开工吧。一个无聊但正确的 C 程序C 程序以 main() 函数开头，通常保存在名为 main.c 的文件中。/* main.c */ int main(int argc, char *argv[]) { }这个程序可以编译但不干任何事。$ gcc main.c $ ./a.out -o foo -vv $正确但无聊。main 函数是唯一的。main() 函数是开始执行时所执行的程序的第一个函数，但不是第一个执行的函数。第一个函数是 _start()，它通常由 C 运行库提供，在编译程序时自动链入。此细节高度依赖于操作系统和编译器工具链，所以我假装没有提到它。main() 函数有两个参数，通常称为 argc 和 argv，并返回一个有符号整数。大多数 Unix 环境都希望程序在成功时返回 0（零），失败时返回 -1（负一）。参数名称描述argc参数个数参数向量的个数argv参数向量字符指针数组参数向量 argv 是调用你的程序的命令行的标记化表示形式。在上面的例子中，argv 将是以下字符串的列表：argv = [ "/path/to/a.out", "-o", "foo", "-vv" ];参数向量在其第一个索引 argv[0] 中确保至少会有一个字符串，这是执行程序的完整路径。main.c 文件的剖析当我从头开始编写 main.c 时，它的结构通常如下：/* main.c */ /* 0 版权/许可证 */ /* 1 包含 */ /* 2 定义 */ /* 3 外部声明 */ /* 4 类型定义 */ /* 5 全局变量声明 */ /* 6 函数原型 */ int main(int argc, char *argv[]) { /* 7 命令行解析 */ } /* 8 函数声明 */下面我将讨论这些编号的各个部分，除了编号为 0 的那部分。如果你必须把版权或许可文本放在源代码中，那就放在那里。另一件我不想讨论的事情是注释。“评论谎言。” - 一个愤世嫉俗但聪明又好看的程序员。与其使用注释，不如使用有意义的函数名和变量名。鉴于程序员固有的惰性，一旦添加了注释，维护负担就会增加一倍。如果更改或重构代码，则需要更新或扩充注释。随着时间的推移，代码会变得面目全非，与注释所描述的内容完全不同。如果你必须写注释，不要写关于代码正在做什么，相反，写下代码为什么要这样写。写一些你将要在五年后读到的注释，那时你已经将这段代码忘得一干二净。世界的命运取决于你。不要有压力。1、包含我添加到 main.c 文件的第一个东西是包含文件，它们为程序提供大量标准 C 标准库函数和变量。C 标准库做了很多事情。浏览 /usr/include 中的头文件，你可以了解到它们可以做些什么。#include 字符串是 C 预处理程序（cpp）指令，它会将引用的文件完整地包含在当前文件中。C 中的头文件通常以 .h 扩展名命名，且不应包含任何可执行代码。它只有宏、定义、类型定义、外部变量和函数原型。字符串 <header.h> 告诉 cpp 在系统定义的头文件路径中查找名为 header.h 的文件，它通常在 /usr/include 目录中。/* main.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <libgen.h> #include <errno.h> #include <string.h> #include <getopt.h> #include <sys/types.h>这是我默认会全局包含的最小包含集合，它将引入：#include 文件提供的东西stdio提供 FILE、stdin、stdout、stderr 和 fprint() 函数系列stdlib提供 malloc()、calloc() 和 realloc()unistd提供 EXIT_FAILURE、EXIT_SUCCESSlibgen提供 basename() 函数errno定义外部 errno 变量及其可以接受的所有值string提供 memcp…