makefile 介绍make 命令执行时，需要一个 makefile 文件，以告诉 make 命令如何编译和链接程序。首先，我们用一个示例来说明 makefile 的书写规则，以便给大家一个感性认识。这个示例来源于 GNU 的 make 使用手册。在这个示例中，我们的工程有 8 个 C 文件和 3 个头文件，我们要写一个 makefile 来告诉 make 命令如何编译和链接这几个文件。我们的规则是：如果这个工程没有编译过，那么所有 C 文件都要编译并被链接。如果这个工程的某几个 C 文件被修改，那么只编译被修改的 C 文件，并链接目标程序。如果这个工程的头文件被改变了，那么需要编译引用了这几个头文件的 C 文件，并链接目标程序。只要我们的 makefile 写得够好，所有的这一切，我们只用一个 make 命令就可以完成。make 命令会自动智能地根据当前的文件修改情况来确定哪些文件需要重编译，从而自己编译所需要的文件并链接目标程序。makefile 的规则在讲述这个 makefile 之前，还是让我们先来粗略地看一看 makefile 的规则。target ... : prerequisites ... command ... ...target：可以是一个 object file（目标文件），也可以是一个执行文件，还可以是一个标签（label）。对于标签这种特性，在后续的“伪目标”章节中会有叙述。prerequisites：就是要生成那个 target 所需要的文件或是目标。command：也就是 make 需要执行的命令（任意的 Shell 命令）。这是一个文件的依赖关系。也就是说，target 这一个或多个的目标文件依赖于 prerequisites 中的文件，其生成规则定义在 command 中。说白一点就是，prerequisites 中如果有一个以上的文件比 target 文件要新的话，command 所定义的命令就会被执行。这就是 makefile 的规则，也就是 makefile 中最核心的内容。说到底，makefile 的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是 makefile 的主线和核心，但要写好一个 makefile 还不够，我会在后面一点一点地结合我的工作经验给你慢慢道来。内容还多着呢。：）一个示例正如前面所说的，如果一个工程有 3 个头文件和 8 个 C 文件，我们为了完成前面所述的那三个规则，我们的 makefile 应该是下面这个样子的。edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o反斜杠（\）是换行符的意思，这样比较便于 makefile 的易读。我们可以把这个内容保存在名字为 makefile 或 Makefile 的文件中，然后在该目录下直接输入命令 make 就可以生成执行文件 edit。如果要删除执行文件和所有的中间目标文件，那么，只要简单地执行一下 make clean 就可以了。在这个 makefile 中，目标文件（target）包含：执行文件 edit 和中间目标文件（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h 文件。每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。在定义好依赖关系后，后续的那一行定义了如何生成目标文件的操作系统命令，一定要以一个 Tab 键作为开头。记住，make 并不管命令是怎么工作的，它只管执行所定义的命令。make 会比较 targets 文件和 prerequisites 文件的修改日期，如果 prerequisites 文件的日期要比 targets 文件的日期要新，或者 target 不存在的话，那么，make 就会执行后续定义的命令。这里要说明一点的是，clean 不是一个文件，它只不过是一个动作名字，有点像 C 语言中的 label 一样。其冒号后什么也没有，那么，make 就不会自动去找它的依赖性，也就不会自动执行其后所定义的命令。要执行其后的命令（不仅用于 clean，其他 label 同样适用），就要在 make 命令后明显地指出这个 label 的名字。这样的方法非常有用，我们可以在一个 makefile 中定义不同的编译或是和编译无关的命令，比如程序的打包、程序的备份等等。make 是如何工作的在默认的方式下，也就是我们只输入 make 命令，那么：make 会在当前目录下找名字叫 Makefile 或 makefile 的文件。如果找到，它会找文件中的第一个目标文件（target）。在上面的例子中，它会找到 edit 这个文件，并把这个文件作为最终的目标文件。如果 edit 文件不存在，或是 edit 所依赖的后面的 .o 文件的文件修改时间要比 edit 这个文件新，那么，它就会执行后面所定义的命令来生成 edit 这个文件。如果 edit 所依赖的 .o 文件也不存在，那么 make 会在当前文件中找目标为 .o 文件的依赖性。如果找到，则再根据那一个规则生成 .o 文件。（这有点像一个堆栈的过程）当然，你的 C 文件和 H 文件是存在的啦，于是 make 会生成 .o 文件，然后再用 .o 文件生成 make 的终极任务，也就是执行文件 edit 了。这就是整个 make 的依赖性。make 会一层又一层地去找文件的依赖关系，直到最终编译出第一个目标文件。在找寻的过程中，如果出现错误，比如最后被依赖的文件找不到，那么 make 就会直接退出并报错。而对于所定义的命令的错误，或是编译不成功，make 根本不理。make 只管文件的依赖性，即：如果在我找了依赖关系之后，冒号后面的文件还是不在，那么对不起，我就不工作啦。通过上述分析，我们知道，像 clean 这种，没有被第一个目标文件直接或间接关联，那么它后面所定义的命令将不会被自动执行。不过，我们可以显式要 make 执行，即命令——make clean，以此来清除所有的目标文件，以便重编译。于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如 file.c，那么根据我们的依赖性，我们的目标 file.o 会被重编译（也就是在这个依赖关系后面所定义的命令）。于是 file.o 的文件也是最新的啦，file.o 的文件修改时间要比 edit 要新，所以 edit 也会被重新链接了（详见 edit 目标文件后定义的命令）。而如果我们改变了 command.h，那么，kbd.o、command.o 和 files.o 都会被重编译，并且，edit 会被重链接。makefile 中使用变量在上面的例子中，先让我们看看 edit 的规则：edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o我们可以看到 .o 文件的字符串被重复了两次。如果我们的工程需要加入一个新的 .o 文件，那么我们需要在两个地方加（应该是三个地方，还有一个地方在 clean 中）。当然，我们的 makefile 并不复杂，所以在两个地方加也不累。但如果 makefile 变得复杂，那么我们就有可能会忘掉一个需要加入的地方，而导致编译失败。所以，为了 makefile 的易维护，在 makefile 中我们可以使用变量。makefile 的变量也就是一个字符串，理解成 C 语言中的宏可能会更好。比如，我们声明任意一变量名，叫 objects、OBJECTS、objs、OBJS、obj 或 OBJ，只要能够表示 obj 文件即可。我们在 makefile 起始处按如下定义此变量：objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o于是，我们就可以很方便地在我们的 makefile 中以 $(objects) 的方式来使用这个变量了。于是我们的改良版 makefile 变为如下：objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit $(objects)如果有新的 .o 文件加入，我们只需简单地修改变量 objects 即可。更多关于变量的话题，我会在后续详细介绍。让 make 自动推导GNU 的 make 很强大，它可以自动推导文件以及文件依赖关系后面的命令。于是我们就没必要去在每一个 .o 文件后都写上类似的命令，因为，我们的 make 会自动识别，并自己推导命令。只要 make 看到一个 .o 文件，它就会自动地把 .c 文件加在依赖关系中。如果 make 找到一个 whatever.o，那么 whatever.c 就会是 whatever.o 的依赖文件，并且 cc -c whatever.c 也会被推导出来。于是，我们的 makefile 再也不用写得这么复杂。我们的新 makefile 又出炉了。objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc = gcc edit : $(objects) cc -o edit $(objects) main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h files.o : defs.h buffer.h command.h utils.o : defs.h .PHONY : clean clean : rm edit $(objects)这种方法，也就是 make 的“隐晦规则”。上面文件内容中，.PHONY 表示 clean 是个伪目标文件。关于更为详细的“隐晦规则”和“伪目标文件”，我会在后续给你一一道来。另类风格的 makefile既然我们的 make 可以自动推导命令，那么我看到那堆 .o 和 .h 的依赖就有点不爽。那么多的重复的 .h，能不能把其收拢起来？好吧，没有问题，这个对于 make 来说很容易，谁叫它提供了自动推导命令和文件的功能呢？来看看最新风格的 makefile 吧。objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) $(objects) : defs.h kbd.o command.o files.o : command.h display.o insert.o search.o files.o : buffer.h .PHONY : clean clean : rm edit $(objects)这种风格，让我们的 makefile 变得很简单，但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得，还看你的喜好了。我是不喜欢这种风格的，一是文件的依赖关系看不清楚，二是如果文件一多，要加入几个新的 .o 文件，那就理不清楚了。清空目标文件的规则每个 Makefile 中都应该写一个清空目标文件（.o 和执行文件）的规则，这不仅便于重编译，也很利于保持文件的清洁。这是一个“修养”（呵呵，还记得我的《编程修养》吗）。一般的风格都是：clean: rm edit $(objects)更为稳健的做法是：.PHONY : clean clean : -rm edit $(objects)前面说过，.PHONY 意思表示 clean 是一个“伪目标”。而在 rm 命令前面加了一个小减号的意思就是，也许某些文件出现问题，但不要管，继续做后面的事。当然，clean 的规则不要放在文件的开头，不然，这就会变成 make 的默认目标，相信谁也不愿意这样。不成文的规矩是——clean 从来都是放在文件的最后。上面就是一个 makefile 的概貌，也是 makefile 的基础。下面还有很多 makefile 的相关细节，准备好了吗？准备好了就来。Makefile 里有什么？Makefile 里主要包含了五个东西：显式规则、隐晦规则、变量定义、文件指示和注释。显式规则：说明了如何生成一个或多个目标文件。这是由 Makefile 的书写者明显指出，要生成的文件、文件的依赖文件、生成的命令。隐晦规则：由于我们的 make 有自动推导的功能，所以隐晦的规则可以让我们比较简略地书写 Makefile，这是由 make 所支持的。变量的定义：在 Makefile 中我们要定义一系列的变量，变量一般都是字符串，这个有点像你 C 语言中的宏。当 Makefile 被执行时，其中的变量都会被扩展到相应的引用位置上。文件指示：其包括了三个部分。一个是在一个 Makefile 中引用另一个 Makefile，就像 C 语言中的 include 一样；另一个是指根据某些情况指定 Makefile 中的有效部分，就像 C 语言中的预编译 #if 一样；还有就是定义一个多行的命令。有关这一部分的内容，我会在后续的部分中讲述。注释：Makefile 中只有行注释，和 UNIX 的 Shell 脚本一样，其注释是用 # 字符，这个就像 C/C++ 中的 // 一样。如果你要在你的 Makefile 中使用 # 字符，可以用反斜杠进行转义，如：\#。最后，还值得一提的是，在 Makefile 中的命令，必须要以 Tab 键开始。Makefile 的文件名默认的情况下，make 命令会在当前目录下按顺序找寻文件名为 GNUmakefile、makefile、Makefile 的文件，找到了解释这个文件。在这三个文件名中，最好使用 Makefile 这个文件名，因为，这个文件名第一个字符为大写，这样有一种显目的感觉。最好不要用 GNUmakefile，这个文件是 GNU 的 make 识别的。有另外一些 make 只对全小写的 makefile 文件名敏感，但是基本上来说，大多数的 make 都支持 makefile 和 Makefile 这两种默认文件名。当然，你可以使用别的文件名来书写 Makefile，比如：Make.Linux、Make.Solaris、Make.AIX 等。如果要指定特定的 Makefile，你可以使用 make 的 -f 和 --file 参数，如：make -f Make.Linux 或 make --file Make.AIX。引用其它的 Makefile在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来，这很像 C 语言的 #include，被包含的文件会原模原样地放在当前文件的包含位置。include 的语法是：include <filename>filename 可以是当前操作系统 Shell 的文件模式（可以包含路径和通配符）。在 include 前面可以有一些空字符，但是绝不能是 Tab 键开始。include 和 <filename> 可以用一个或多个空格隔开。举个例子，你有这样几个 Makefile：a.mk、b.mk、c.mk，还有一个文件叫 foo.make，以及一个变量 $(bar)，其包含了 e.mk 和 f.mk，那么，下面的语句：include foo.make *.mk $(bar)等价于：include foo.make a.mk b.mk c.mk e.mk f.mkmake 命令开始时，会找寻 include 所指出的其它 Makefile，并把其内容安置在当前的位置。就好像 C/C++ 的 #include 指令一样。如果文件都没有指定绝对路径或是相对路径的话，make 会在当前目录下首先寻找。如果当前目录下没有找到，那么，make 还会在下面的几个目录下找：如果 make 执行时，有 -I 或 --include-dir 参数，那么 make 就会在这个参数所指定的目录下去寻找。如果目录 <prefix>/include（一般是：/usr/local/bin 或 /usr/include）存在的话，make 也会去找。如果有文件没有找到的话，make 会生成一条警告信息，但不会马上出现致命错误。它会继续载入其它的文件。一旦完成 makefile 的读取，make 会再重试这些没有找到，或是不能读取的文件。如果还是不行，make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件，而继续执行，你可以在 include 前加一个减号 -。如：-include <filename>其表示，无论 include 过程中出现什么错误，都不要报错继续执行。和其它版本 make 兼容的相关命令是 sinclude，其作用和这一个是一样的。环境变量 MAKEFILES如果你的当前环境中定义了环境变量 MAKEFILES，那么，make 会把这个变量中的值做一个类似于 include 的动作。这个变量中的值是其它的 Makefile，用空格分隔。只是，它和 include 不同的是，从这个环境变量中引入的 Makefile 的“目标”不会起作用。如果环境变量中定义的文件发现错误，make 也会不理。但是在这里我还是建议不要使用这个环境变量，因为只要这个变量一被定义，那么当你使用 make 时，所有的 Makefile 都会受到它的影响，这绝不是你想看到的。在这里提这个事，只是为了告诉大家，也许有时候你的 Makefile 出现了怪事，那么你可以看看当前环境中有没有定义这个变量。make 的工作方式GNU 的 make 工作时的执行步骤如下：（想来其它的 make 也是类似）读入所有的 Makefile。读入被 include 的其它 Makefile。初始化文件中的变量。推导隐晦规则，并分析所有规则。为所有的目标文件创建依赖关系链。根据依赖关系，决定哪些目标要重新生成。执行生成命令。1-5 步为第一个阶段，6-7 为第二个阶段。第一个阶段中，如果定义的变量被使用了，那么，make 会把其展开在使用的位置。但 make 并不会完全马上展开，make 使用的是拖延战术。如果变量出现在依赖关系的规则中，那么仅当这条依赖被决定要使用了，变量才会在其内部展开。当然，这个工作方式你不一定要清楚，但是知道这个方式你也会对 make 更为熟悉。有了这个基础，后续部分也就容易看懂了。说明：本文基于 GNU Make 经典教程整理，核心规则适用于大多数 Make 版本。文中示例使用的 cc 命令在实际现代工程中建议替换为 $(CC) 隐式变量，以增强跨平台兼容性。
问题背景近期开发了一个服务器程序，在 Linux 环境下测试时，使用 C++ 编写客户端以千万级数量的短连接进行压力测试。测试过程中，服务器进程总是莫名退出，且没有生成 core 文件。经过排查，问题最终定位为：对一个对端已经关闭的 socket 调用两次 write，第二次调用将会生成 SIGPIPE 信号，而该信号的默认处理动作是终止进程。原因分析1. TCP 连接关闭语义具体的分析可以结合 TCP 的“四次握手”关闭过程。TCP 是全双工信道，可以看作两条单工信道，连接两端的端点各负责一条。当对端调用 close 时，虽然本意是关闭整个连接，但本端首先收到的是 FIN 包。按照 TCP 协议语义，这表示对端关闭了其所负责的那一条单工信道（不再发送数据），但本端仍然可以继续接收数据（即本端的发送信道尚未关闭）。受限于 TCP 协议，一个端点无法直接获知对端的 socket 是调用了 close 还是 shutdown。2. SIGPIPE 信号触发流程Read 阶段：对一个已经收到 FIN 包的 socket 调用 read 方法，如果接收缓冲已空，则返回 0。这通常被视为连接关闭的标志。第一次 Write：此时若第一次调用 write 方法，且发送缓冲没问题，内核会返回正确写入（数据进入发送缓冲）。但该报文到达对端后，会因为对端 socket 已完全关闭（既不发送也不接收）而触发对端发送 RST 报文。第二次 Write：当本端收到 RST 报文后，再次调用 write 方法时，内核会生成 SIGPIPE 信号。进程退出：由于 SIGPIPE 信号的默认处理动作是终止进程，导致服务器异常退出且无 core 文件（因为信号默认终止通常不产生 core dump，除非配置了）。解决方案为了避免进程因 SIGPIPE 信号意外退出，可以捕获该信号，或者更简单地——忽略它。通过设置信号处理函数为 SIG_IGN，可以屏蔽该信号：signal(SIGPIPE, SIG_IGN);设置忽略后，当再次向已关闭的 socket 调用 write 方法时：不会产生信号。函数返回 -1。errno 被置为 EPIPE（注意：原文明确为 SIGPIPE 有误，实际 errno 值为 EPIPE）。程序便可通过返回值和 errno 知道对端已经关闭，从而进行相应的错误处理，而不是直接崩溃。在 Linux 下编写 socket 程序时，如果尝试 send 到一个 disconnected socket 上，底层就会抛出 SIGPIPE 信号。这个信号的缺省处理方法是退出进程，大多数时候这都不是我们期望的。因此，重载这个信号的处理方法是必要的。信号处理机制详解对于产生信号，我们可以在产生信号前利用方法 signal(int signum, sighandler_t handler) 设置信号的处理。如果没有调用此方法，系统就会调用默认处理方法：中止程序，显示提示信息。我们可以调用系统的处理方法，也可以自定义处理方法。系统里主要定义了以下几种处理动作：SIG_DFL（信号专用的默认动作）如果默认动作是暂停线程，则该线程的执行被暂时挂起。当线程暂停期间，发送给线程的任何附加信号都不交付，直到该线程开始执行（SIGKILL 除外）。把挂起信号的信号动作设置成 SIG_DFL，且其默认动作是忽略信号（如 SIGCHLD）。SIG_IGN（忽略信号）该信号的交付对线程没有影响。系统不允许把 SIGKILL 或 SIGSTOP 信号的动作设置为 SIG_IGN 或 SIG_DFL。SIG_ERR这通常不是处理动作，而是 signal() 函数出错时的返回值指示。在本项目中，调用了 signal(SIGPIPE, SIG_IGN)，这样产生 SIGPIPE 信号时就不会中止程序，直接把这个信号忽略掉，保证了服务器的稳定性。扩展：僵尸进程处理如果服务器采用了 fork 创建子进程，需要收集垃圾进程，防止僵尸进程的产生。可以类似地处理 SIGCHLD 信号：signal(SIGCHLD, SIG_IGN); // 交给系统 init 去回收这里设置忽略 SIGCHLD 后，子进程退出时就不会产生僵尸进程了，系统会自动回收资源。说明：本文基于 Linux/Unix 环境下的标准 POSIX 信号机制。不同系统对 signal() 语义的实现可能存在细微差异（如 BSD 与 System V），但在 SIGPIPE 和 SIG_IGN 的行为上基本一致。errno 在写失败时通常设置为 EPIPE。
值得学习的 C 语言开源项目以下精选了 10 个值得深入学习的 C 语言开源项目，涵盖了 Web 压测、服务器、数据处理、单元测试、事件驱动等多个领域。这些项目代码简洁、结构清晰，非常适合初学者阅读源码以提升编程能力。1. WebbenchWebbench 是一个在 Linux 下使用的非常简单的网站压测工具。它使用 fork() 模拟多个客户端同时访问设定的 URL，测试网站在压力下的工作性能，最多可以模拟 3 万个并发连接以测试网站的负载能力。Webbench 使用 C 语言编写，代码极其简洁，源码总计不到 600 行。下载链接：http://home.tiscali.cz/~cz210552/webbench.html2. TinyhttpdTinyhttpd 是一个超轻量型 HTTP Server，使用 C 语言开发，全部代码只有 502 行（包括注释）。它附带一个简单的 Client，可以通过阅读这段代码理解一个 HTTP Server 的本质。下载链接：http://sourceforge.net/projects/tinyhttpd/3. cJSONcJSON 是 C 语言中的一个 JSON 编解码器，非常轻量级，C 文件只有 500 多行，速度也非常理想。cJSON 也存在几个弱点，虽然功能不是非常强大，但 cJSON 的小身板和速度是最值得赞赏的。其代码被非常好地维护着，结构也简单易懂，可以作为一个非常好的 C 语言项目进行学习。项目主页：http://sourceforge.net/projects/cjson/4. CMockeryCMockery 是 Google 发布的用于 C 单元测试的一个轻量级框架。它很小巧，对其他开源包没有依赖，对被测试代码侵入性小。CMockery 的源代码行数不到 3K，阅读一下 will_return 和 mock 的源代码就一目了然了。主要特点：免费且开源，Google 提供技术支持；轻量级的框架，使测试更加快速简单；避免使用复杂的编译器特性，对老版本的编译器来讲，兼容性好；并不强制要求待测代码必须依赖 C99 标准，这一特性对许多嵌入式系统的开发很有用。下载链接：http://code.google.com/p/cmockery/downloads/list5. LibevLibev 是一个开源的事件驱动库，基于 epoll、kqueue 等 OS 提供的基础设施。其以高效出名，它可以将 IO 事件、定时器和信号统一起来，放在事件处理这一套框架下处理。基于 Reactor 模式，效率较高，并且代码精简（4.15 版本 8000 多行），是学习事件驱动编程的很好的资源。下载链接：http://software.schmorp.de/pkg/libev.html6. MemcachedMemcached 是一个高性能的分布式内存对象缓存系统，用于动态 Web 应用以减轻 数据库 负载。它通过在内存中缓存数据和对象来减少读取数据库的次数，从而提供动态数据库驱动网站的速度。Memcached 基于一个存储键/值对的 hashmap。Memcached-1.4.7 的代码量还是可以接受的，只有 10K 行左右。下载地址：http://memcached.org/7. LuaLua 非常出色。虽然它是由巴西人发明的，但这并不影响其卓越的品质，反而令人羡慕。让人印象深刻的是 Lua 的源代码，百分之一百的 ANSI C，一点都不掺杂。在任何支持 ANSI C 编译器的平台上都可以轻松编译通过。实测证明，代码真是一点废话都没有。Lua 的代码数量足够小，5.1.4 版本仅仅 1.5 万行，去掉空白行和注释估计能到 1 万行。下载地址：http://www.lua.org/8. SQLiteSQLite 是一个开源的嵌入式关系数据库，实现自包容、零配置、支持事务的 SQL 数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。足够小，大致 3 万行 C 代码，250K。下载地址：http://www.sqlite.org/9. UNIX v6UNIX V6 的内核源代码包括设备驱动程序在内约有 1 万行，这个数量的源代码，初学者是能够充分理解的。有一种说法是一个人所能理解的代码量上限为 1 万行，UNIX V6 的内核源代码从数量上看正好在这个范围之内。看到这里，大家是不是也有“如果只有 1 万行的话没准儿我也能学会”的想法呢？另一方面，最近的操作系统，例如 Linux 最新版的内核源代码据说超过了 1000 万行。就算不是初学者，想完全理解全部代码基本上也是不可能的。下载地址：http://minnie.tuhs.org/cgi-bin/utree.pl?file=V610. NETBSDNetBSD 是一个免费的、具有高度移植性的 UNIX-like 操作系统，是现行可移植平台最多的操作系统，可以在许多平台上执行，从 64bit Alpha 服务器到手持设备和嵌入式设备。NetBSD 计划的口号是："Of course it runs NetBSD"。它设计简洁，代码规范，拥有众多先进特性，使得它在业界和学术界广受好评。由于简洁的设计和先进的特征，使得它在生产和研究方面都有卓越的表现，而且它也有受使用者支持的完整的源代码。许多程序都可以很容易地通过 NetBSD Packages Collection 获得。下载地址：http://www.netbsd.org/C++ 资源大全以下是关于 C++ 框架、库和资源的一些汇总列表，内容包括：标准库、Web 应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。标准库C++ 标准库，包括了 STL 容器、算法和函数等。C++ Standard Library：是一系列类和函数的集合，使用核心语言编写，也是 C++ ISO 自身标准的一部分。Standard Template Library：标准模板库。C POSIX library：POSIX 系统的 C 标准库规范。ISO C++ Standards Committee：C++ 标准委员会。框架C++ 通用框架和库。Apache C++ Standard Library：是一系列算法、容器、迭代器和其他基本组件的集合。ASL：Adobe 源代码库提供了同行的评审和可移植的 C++ 源代码库。Boost：大量通用 C++ 库的集合。BDE：来自于彭博资讯实验室的开发环境。Cinder：提供专业品质创造性编码的开源开发社区。Cxxomfort：轻量级的、只包含头文件的库，将 C++ 11 的一些新特性移植到 C++03 中。Dlib：使用契约式编程和现代 C++ 科技设计的通用的跨平台的 C++ 库。EASTL：EA-STL 公共部分。ffead-cpp：企业应用程序开发框架。Folly：由 Facebook 开发和使用的开源 C++ 库。JUCE：包罗万象的 C++ 类库，用于开发跨平台软件。libPhenom：用于构建高性能和高度可扩展性系统的事件框架。LibSourcey：用于实时的视频流和高性能网络应用程序的 C++11 evented IO。LibU：C 语言写的多平台工具库。Loki：C++ 库的设计，包括常见的设计模式和习语的实现。MiLi：只含头文件的小型 C++ 库。openFrameworks：开发 C++ 工具包，用于创意性编码。Qt：跨平台的应用程序和用户界面框架。Reason：跨平台的框架，使开发者能够更容易地使用 Java、.Net 和 Python，同时也满足了他们对 C++ 性能和优势的需求。ROOT：具备所有功能的一系列面向对象的框架，能够非常高效地处理和分析大量的数据，为欧洲原子能研究机构所用。STLport：是 STL 具有代表性的版本。STXXL：用于额外的大型数据集的标准模板库。Ultimate++：C++ 跨平台快速应用程序开发框架。Windows Template Library：用于开发 Windows 应用程序和 UI 组件的 C++ 库。Yomm11：C++11 的开放 multi-methods。人工智能btsk：游戏行为树启动器工具。Evolving Objects：基于模板的、ANSI C++ 演化计算库，能够帮助你非常快速地编写出自己的随机优化算法。Neu：C++11 框架，编程语言集，用于创建人工智能应用程序的多用途软件系统。异步事件循环Boost.Asio：用于网络和底层 I/O 编程的跨平台的 C++ 库。libev：功能齐全、高性能的时间循环，轻微地仿效 libevent，但是不再像 libevent 一样有局限性，也修复了它的一些 bug。libevent：事件通知库。libuv：跨平台异步 I/O。音频音频、声音、音乐、数字化音乐库。FMOD：易于使用的跨平台的音频引擎和音频内容的游戏创作工具。Maximilian：C++ 音频和音乐数字信号处理库。OpenAL：开源音频库—跨平台的音频 API。Opus：一个完全开放的、免版税的、高度通用的音频编解码器。Speex：免费编解码器，为 Opus 所废弃。Tonic：C++ 易用和高效的音频合成。Vorbis：Ogg Vorbis 是一种完全开放的、非专有的、免版税的通用压缩音频格式。生态学生物信息、基因组学和生物技术。libsequence：用于表示和分析群体遗传学数据的 C++ 库。SeqAn：专注于生物数据序列分析的算法和数据结构。Vcflib：用于解析和处理 VCF 文件的 C++ 库。Wham：直接把联想测试应用到 BAM 文件的基因结构变异。压缩压缩和归档库。bzip2：一个完全免费、免费专利和高质量的数据压缩。doboz：能够快速解压缩的压缩库。PhysicsFS：对各种归档提供抽象访问的库，主要用于视频游戏，设计灵感部分来自于 Quake3 的文件子系统。KArchive：用于创建、读写和操作文件档案（例如 zip 和 tar）的库，它通过 QIODevice 的一系列子类，使用 gzip 格式，提供了透明的压缩和解压缩的数据。LZ4：非常快速的压缩算法。LZHAM：无损压缩数据库，压缩比率跟 LZMA 接近，但是解压缩速度却要快得多。LZMA：7z 格式默认和通用的压缩方法。LZMAT：及其快速的实时无损数据压缩库。miniz：单一的 C 源文件，紧缩/膨胀压缩库，使用 zlib 兼容 API，ZIP 归档读写，PNG 写方式。Minizip：Zlib 最新 bug 修复，支持 PKWARE 磁盘跨越、AES 加密和 IO 缓冲。Snappy：快速压缩和解压缩。ZLib：非常紧凑的数据流压缩库。ZZIPlib：提供 ZIP 归档的读权限。并发性并发执行和多线程。Boost.Compute：用于 OpenCL 的 C++ GPU 计算库。Bolt：针对 GPU 进行优化的 C++ 模板库。C++React：用于 C++11 的反应性编程库。Intel TBB：Intel 线程构件块。Libclsph：基于 OpenCL 的 GPU 加速 SPH 流体仿真库。OpenCL：并行编程的异构系统的开放标准。OpenMP：OpenMP API。Thrust：类似于 C++ 标准模板库的并行算法库。HPX：用于任何规模的并行和分布式应用程序的通用 C++ 运行时系统。VexCL：用于 OpenCL/CUDA 的 C++ 向量表达式模板库。容器C++ B-tree：基于 B 树数据结构，实现命令内存容器的模板库。Hashmaps：C++ 中开放寻址哈希表算法的实现。密码学Bcrypt：一个跨平台的文件加密工具，加密文件可以移植到所有可支持的操作系统和处理器中。BeeCryptBotan：C++ 加密库。Crypto++：一个有关加密方案的免费的 C++ 库。GnuPG：OpenPGP 标准的完整实现。GnuTLS：实现了 SSL、TLS 和 DTLS 协议的安全通信库。LibgcryptlibmcryptLibreSSL：免费的 SSL/TLS 协议，属于 2014 OpenSSL 的一个分支。LibTomCrypt：一个非常全面的、模块化的、可移植的加密工具。libsodium：基于 NaCI 的加密库，固执己见，容易使用。Nettle：底层的加密库。OpenSSL：一个强大的、商用的、功能齐全的、开放源代码的加密库。Tiny AES128 in C：用 C 实现的一个小巧、可移植的实现了 AES128ESB 的加密算法。数据库数据库、SQL 服务器、ODBC 驱动程序和工具。hiberlite：用于 Sqlite3 的 C++ 对象关系映射。Hiredis：用于 Redis 数据库的很简单的 C 客户端库。LevelDB：快速键值存储库。LMDB：符合数据库四大基本元素的嵌入键值存储。MySQL++：封装了 MySql 的 C API 的 C++ 包装器。RocksDB：来自 Facebook 的嵌入键值的快速存储。SQLite：一个完全嵌入式的、功能齐全的关系数据库，只有几百 KB，可以正确包含到你的项目中。调试调试库、内存和资源泄露检测、单元测试。Boost.Test：Boost 测试库。Catch：一个很时尚的、C++ 原生的框架，只包含头文件，用于单元测试、测试驱动开发和行为驱动开发。CppUnit：由 JUnit 移植过来的 C++ 测试框架。CTest：CMake 测试驱动程序。googletest：谷歌 C++ 测试框架。ig-debugheap：用于跟踪内存错误的多平台调试堆。libtap：用 C 语言编写测试。MemTrack：用于 C++ 跟踪内存分配。microprofile：跨平台的网络试图分析器。minUnit：使用 C 写的迷你单元测试框架，只使用了两个宏。Remotery：用于 web 视图的单一 C 文件分析器。UnitTest++：轻量级的 C++ 单元测试框架。游戏引擎Cocos2d-x：一个跨平台框架，用于构建 2D 游戏、互动图书、演示和其他图形应用程序。Grit：社区项目，用于构建一个免费的游戏引擎，实现开放的世界 3D 游戏。Irrlicht：C++ 语言编写的开源高性能的实时 3D 引擎。Polycode：C++ 实现的用于创建游戏的开源框架（与 Lua 绑定）。图形用户界面CEGUI：很灵活的跨平台 GUI 库。FLTK：快速、轻量级的跨平台的 C++ GUI 工具包。GTK+：用于创建图形用户界面的跨平台工具包。gtkmm：用于受欢迎的 GUI 库 GTK+ 的官方 C++ 接口。imgui：拥有最小依赖关系的立即模式图形用户界面。libRocket：libRocket 是一个 C++ HTML/CSS 游戏接口中间件。MyGUI：快速、灵活、简单的 GUI。Ncurses：终端用户界面。QCustomPlot：没有更多依赖关系的 Qt 绘图控件。Qwt：用户与技术应用的 Qt 控件。QwtPlot3D：功能丰富的基于 Qt/OpenGL 的 C++ 编程库，本质上提供了一群 3D 控件。OtterUI：OtterUI 是用于嵌入式系统和互动娱乐软件的用户界面开发解决方案。PDCurses：包含源代码和预编译库的公共图形函数库。wxWidgets：C++ 库，允许开发人员使用一个代码库可以为 Windows、Mac OS X、Linux 和其他平台创建应用程序。图形bgfx：跨平台的渲染库。Cairo：支持多种输出设备的 2D 图形库。Horde3D：一个小型的 3D 渲染和动画引擎。magnum：C++11 和 OpenGL 2D/3D 图形引擎。Ogre 3D：用 C++ 编写的一个面向场景、实时、灵活的 3D 渲染引擎（并非游戏引擎）。OpenSceneGraph：具有高性能的开源 3D 图形工具包。Panda3D：用于 3D 渲染和游戏开发的框架，用 Python 和 C++ 编写。Skia：用于绘制文字、图形和图像的完整的 2D 图形库。urho3d：跨平台的渲染和游戏引擎。图像处理Boost.GIL：通用图像库。CImg：用于图像处理的小型开源 C++ 工具包。CxImage：用于加载、保存、显示和转换的图像处理和转换库，可以处理的图片格式包括 BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K。FreeImage：开源库，支持现在多媒体应用所需的通用图片格式和其他格式。GDCM：Grassroots DICOM 库。ITK：跨平台的开源图像分析系统。Magick++：ImageMagick 程序的 C++ 接口。MagickWnd：ImageMagick 程序的 C++ 接口。OpenCV：开源计算机视觉类库。tesseract-ocr：OCR 引擎。VIGRA：用于图像分析通用 C++ 计算机视觉库。VTK：用于 3D 计算机图形学、图像处理和可视化的开源免费软件系统。国际化gettext：GNU gettext。IBM ICU：提供 Unicode 和全球化支持的 C、C++ 和 Java 库。libiconv：用于不同字符编码之间的编码转换库。JSONfrozen：C/C++ 的 JSON 解析生成器。Jansson：进行编解码和处理 JSON 数据的 C 语言库。jbson：C++14 中构建和迭代 BSON data 和 JSON 文档的库。JeayeSON：非常健全的 C++ JSON 库，只包含头文件。JSON++：C++ JSON 解析器。json-parser：用可移植的 ANSI C 编写的 JSON 解析器，占用内存非常少。json11：一个迷你的 C++11 JSON 库。jute：非常简单的 C++ JSON 解析器。libjson：C 语言中的 JSON 解析和打印库，很容易和任何模型集成。libjson：轻量级的 JSON 库。PicoJSON：C++ 中 JSON 解析序列化，只包含头文件。qt-json：用于 JSON 数据和 QVariant 层次间的相互解析的简单类。QJson：将 JSON 数据映射到 QVariant 对象的基于 Qt 的库。RapidJSON：用于 C++ 的快速 JSON 解析生成器，包含 SAX 和 DOM 两种风格的 API。YAJL：C 语言中快速流 JSON 解析库。日志Boost.Log：设计非常模块化，并且具有扩展性。easyloggingpp：C++ 日志库，只包含单一的头文件。Log4cpp：一系列 C++ 类库，灵活添加日志到文件、系统日志、IDSA 和其他地方。templog：轻量级 C++ 库，可以添加日志到你的 C++ 应用程序中。机器学习Caffe：快速的神经网络框架。CCV：以 C 语言为核心的现代计算机视觉库。mlpack：可扩展的 C++ 机器学习库。OpenCV：开源计算机视觉库。Recommender：使用协同过滤进行产品推荐/建议的 C 语言库。SHOGUN：Shogun 机器学习工具。sofia-ml：用于机器学习的快速增量算法套件。数学Armadillo：高质量的 C++ 线性代数库，速度和易用性做到了很好的平衡。语法和 MATLAB 很相似。blaze：高性能的 C++ 数学库，用于密集和稀疏算法。ceres-solver：来自谷歌的 C++ 库，用于建模和解决大型复杂非线性最小平方问题。CGal：高效、可靠的集合算法集合。cml：用于游戏和图形的免费 C++ 数学库。Eigen：高级 C++ 模板头文件库，包括线性代数、矩阵、向量操作、数值解决和其他相关的算法。GMTL：数学图形模板库是一组广泛实现基本图形的工具。GMP：用于个高精度计算的 C/C++ 库，处理有符号整数、有理数和浮点数。多媒体GStreamer：构建媒体处理组件图形的库。LIVE555 Streaming Media：使用开放标准协议 (RTP/RTCP, RTSP, SIP) 的多媒体流库。libVLC：libVLC (VLC SDK) 媒体框架。QtAv：基于 Qt 和 FFmpeg 的多媒体播放框架，能够帮助你轻而易举地编写出一个播放器。SDL：简单直控媒体层。SFML：快速、简单的多媒体库。网络ACE：C++ 面向对象网络编程工具包。Boost.Asio：用于网络和底层 I/O 编程的跨平台的 C++ 库。Casablanca：C++ REST SDK。cpp-netlib：高级网络编程的开源库集合。Dyad.c：C 语言的异步网络。libcurl：多协议文件传输库。Mongoose：非常轻量级的网络服务器。Muduo：用于 Linux 多线程服务器的 C++ 非阻塞网络库。net_skeleton：C/C++ 的 TCP 客户端/服务器库。nope.c：基于 C 语言的超轻型软件平台，用于可扩展的服务器端和网络应用。对于 C 编程人员，可以考虑 node.js。Onion：C 语言 HTTP 服务器库，其设计为轻量级、易使用。POCO：用于构建网络和基于互联网应用程序的 C++ 类库，可以运行在桌面、服务器、移动和嵌入式系统。RakNet：为游戏开发人员提供的跨平台的开源 C++ 网络引擎。Tuf o：用于 Qt 之上的 C++ 构建的异步 Web 框架。WebSocket++：基于 C++/Boost Asio 的 websocket 客户端/服务器库。ZeroMQ：高速、模块化的异步通信库。物理学动力学仿真引擎。Box2D：2D 的游戏物理引擎。Bullet：3D 的游戏物理引擎。Chipmunk：快速、轻量级的 2D 游戏物理库。LiquidFun：2D 的游戏物理引擎。ODE：开放动力学引擎 - 开源、高性能库，模拟刚体动力学。ofxBox2d：Box2D 开源框架包装器。Simbody：高性能 C++ 多体动力学/物理库，模拟关节生物力学和机械系统，像车辆、机器人和人体骨骼。机器人学MOOS-IvP：一组开源 C++ 模块，提供机器人平台的自主权，尤其是自主的海洋车辆。MRPT：移动机器人编程工具包。PCL：点云库是一个独立的、大规模的开放项目，用于 2D/3D 图像和点云处理。Robotics Library (RL)：一个独立的 C++ 库，包括机器人动力学、运动规划和控制。RobWork：一组 C++ 库的集合，用于机器人系统的仿真和控制。ROS：机器人操作系统，提供了一些库和工具帮助软件开发人员创建机器人应用程序。科学计算FFTW：用一维或者多维计算 DFT 的 C 语言库。GSL：GNU 科学库。脚本ChaiScript：用于 C++ 的易于使用的嵌入式脚本语言。Lua：用于配置文件和基本应用程序脚本的小型快速脚本引擎。luacxx：用于创建 Lua 绑定的 C++ 11 API。SWIG：一个可以让你的 C++ 代码链接到 JavaScript、Perl、PHP、Python、Tcl 和 Ruby 的包装器/接口生成器。V7：嵌入式的 JavaScript 引擎。V8：谷歌的快速 JavaScript 引擎，可以被嵌入到任何 C++ 应用程序中。序列化Cap'n Proto：快速数据交换格式和 RPC 系统。cereal：C++11 序列化库。FlatBuffers：内存高效的序列化库。MessagePack：C/C++ 的高效二进制序列化库，例如 JSON。protobuf：协议缓冲，谷歌的数据交换格式。protobuf-c：C 语言的协议缓冲实现。SimpleBinaryEncoding：用于低延迟应用程序的对二进制格式的应用程序信息的编码和解码。Thrift：高效的跨语言 IPC/RPC，用于 C++、Java、Python、PHP、C# 和其它多种语言中，最初由 Twitter 开发。视频libvpx：VP8/VP9 编码解码 SDK。FFmpeg：一个完整的、跨平台的解决方案，用于记录、转换视频和音频流。libde265：开放的 h.265 视频编解码器的实现。OpenH264：开源 H.264 编解码器。Theora：免费开源的视频压缩格式。虚拟机CarpVM：C 中有趣的 VM，让我们一起来看看这个。MicroPython：旨在实现单片机上 Python3.x 的实现。TinyVM：用纯粹的 ANSI C 编写的小型、快速、轻量级的虚拟机。Web 应用框架Civetweb：提供易于使用、强大的、C/C++ 嵌入式 Web 服务器，带有可选的 CGI、SSL 和 Lua 支持。CppCMS：免费高性能的 Web 开发框架（不是 CMS）。Crow：一个 C++ 微型 web 框架（灵感来自于 Python Flask）。Kore：使用 C 语言开发的用于 web 应用程序的超快速和灵活的 web 服务器/框架。libOnion：轻量级的库，帮助你使用 C 编程语言创建 web 服务器。QDjango：使用 C++ 编写的、基于 Qt 库的 web 框架，试图效仿 Django API，因此得此名。Wt：开发 Web 应用的 C++ 库。XMLXML 就是个垃圾，xml 的解析很烦人，对于计算机它也是个灾难。这种糟糕的东西完全没有存在的理由了。 - Linus TorvaldsExpat：用 C 语言编写的 xml 解析库。Libxml2：Gnome 的 xml C 解析器和工具包。libxml++：C++ 的 xml 解析器。PugiXML：用于 C++ 的、支持 XPath 的轻量级、简单快速的 XML 解析器。RapidXml：试图创建最快速的 XML 解析器，同时保持易用性、可移植性和合理的 W3C 兼容性。TinyXML：简单小型的 C++ XML 解析器，可以很容易地集成到其它项目中。TinyXML2：简单快速的 C++ XML 解析器，可以很容易集成到其它项目中。TinyXML++：TinyXML 的一个全新的接口，使用了 C++ 的许多许多优势，模板、异常和更好的异常处理。Xerces-C++：用可移植的 C++ 的子集编写的 XML 验证解析器。其他工具与库一些有用的库或者工具，但是不适合上面的分类，或者还没有分类。C++ Format：C++ 的小型、安全和快速格式化库。casacore：从 aips++ 派生的一系列 C++ 核心库。cxx-prettyprint：用于 C++ 容器的打印库。DynaPDF：易于使用的 PDF 生成库。gcc-poison：帮助开发人员禁止应用程序中的不安全的 C/C++ 函数的简单的头文件。googlemock：编写和使用 C++ 模拟类的库。HTTP Parser：C 的 http 请求/响应解析器。libcpuid：用于 x86 CPU 检测和特征提取的小型 C 库。libevil：许可证管理器。libusb：允许移动访问 USB 设备的通用 USB 库。PCRE：正则表达式 C 库，灵感来自于 Perl 中正则表达式的功能。Remote Call Framework：C++ 的进程间通信框架。Scintilla：开源的代码编辑控件。Serial Communication Library：C++ 语言编写的跨平台、串口库。SDS：C 的简单动态字符串库。SLDR：超轻的 DNS 解析器。SLRE：超轻的正则表达式库。Stage：移动机器人模拟器。VarTypes：C++/Qt4 功能丰富、面向对象的管理变量的框架。ZBar：'条形码扫描器'库，可以扫描照片、图片和视频流中的条形码，并返回结果。CppVerbalExpressions：易于使用的 C++ 正则表达式。QtVerbalExpressions：基于 C++ VerbalExpressions 库的 Qt 库。PHP-CPP：使用 C++ 来构建 PHP 扩展的库。Better String：C 的另一个字符串库，功能更丰富，但是没有缓冲溢出问题，还包含了一个 C++ 包装器。开发环境与工具用于创建开发环境的软件。编译器C/C++ 编译器列表。Clang：由苹果公司开发的。GCC：GNU 编译器集合。Intel C++ Compiler：由英特尔公司开发。LLVM：模块化和可重用编译器和工具链技术的集合。Microsoft Visual C++：MSVC，由微软公司开发。Open WatCom：Watcom，C、C++ 和 Fortran 交叉编译器和工具。TCC：轻量级的 C 语言编译器。在线编译器在线 C/C++ 编译器列表。codepad：在线编译器/解释器，一个简单的协作工具。CodeTwist：一个简单的在线编译器/解释器，你可以粘贴的 C、C++ 或者 Java 代码，在线执行并查看结果。coliru：在线编译器/shell，支持各种 C++ 编译器。Compiler Explorer：交互式编译器，可以进行汇编输出。CompileOnline：Linux 上在线编译和执行 C++ 程序。Ideone：一个在线编译器和调试工具，允许你在线编译源代码并执行，支持 60 多种编程语言。调试器C/C++ 调试器列表。Comparison of debuggers：来自维基百科的调试器列表。GDB：GNU 调试器。Valgrind：内存调试、内存泄露检测、性能分析工具。集成开发环境（IDE）C/C++ 集成开发环境列表。AppCode：构建与 JetBrains' IntelliJ IDEA 平台上的用于 Objective-C、C、C++、Java 和 Java 开发的集成开发环境。CLion：来自 JetBrains 的跨平台的 C/C++ 的集成开发环境。Code::Blocks：免费 C、C++ 和 Fortran 的集成开发环境。CodeLite：另一个跨平台的免费的 C/C++ 集成开发环境。Dev-C++：可移植的 C/C++/C++11 集成开发环境。Eclipse CDT：基于 Eclipse 平台的功能齐全的 C 和 C++ 集成开发环境。Geany：轻量级的快速、跨平台的集成开发环境。IBM VisualAge：来自 IBM 的家庭计算机集成开发环境。Irony-mode：由 libclang 驱动的用于 Emacs 的 C/C++ 微模式。KDevelop：免费开源集成开发环境。Microsoft Visual Studio：来自微软的集成开发环境。NetBeans：主要用于 Java 开发的的集成开发环境，也支持其他语言，尤其是 PHP、C/C++ 和 HTML5。Qt Creator：跨平台的 C++、Javascript 和 QML 集成开发环境，也是 Qt SDK 的一部分。rtags：C/C++ 的客户端服务器索引，用于 跟基于 clang 的 emacs 的集成。Xcode：由苹果公司开发。YouCompleteMe：一个用于 Vim 的根据你敲的代码快速模糊搜索并进行代码补全的引擎。构建系统Bear：用于为 clang 工具生成编译数据库的工具。Biicode：基于文件的简单依赖管理器。CMake：跨平台的免费开源软件用于管理软件使用独立编译的方法进行构建的过程。CPM：基于 CMake 和 Git 的 C++ 包管理器。FASTBuild：高性能、开源的构建系统，支持高度可扩展性的编译、缓冲和网络分布。Ninja：专注于速度的小型构建系统。Scons：使用 Python script 配置的软件构建工具。tundra：高性能的代码构建系统，甚至对于非常大型的软件项目，也能提供最好的增量构建次数。tup：基于文件的构建系统，用于后台监控变化的文件。静态代码分析提高质量、减少瑕疵的代码分析工具列表。Cppcheck：静态 C/C++ 代码分析工具。include-what-you-use：使用 clang 进行代码分析的工具，可以#include 在 C 和 C++ 文件中。OCLint：用于 C、C++ 和 Objective-C 的静态源代码分析工具，用于提高质量、减少瑕疵。Clang Static Analyzer：查找 C、C++ 和 Objective-C 程序 bug 的源代码分析工具。List of tools for static code analysis：来自维基百科的静态代码分析工具列表。说明时效性提示：本文部分链接指向的项目（如 Google Code 托管的项目）可能因服务关闭而无法访问，部分库可能已迁移至 GitHub 或其他平台。建议在实际使用时搜索最新官方仓库。版本适用：文中提到的代码行数及特性基于文章撰写时的版本，新版本可能有所变化。
Socket编程（C语言实现）——基于TCP协议，基于UDP协议(多线程，循环监听）(网络间通信AF_INET，典型的TCP/IP四型模型的通信过程)
Socket 编程基础与 C 语言实现目前较为流行的网络编程模型是客户机/服务器（Client/Server）通信模式。客户进程向服务器进程发出请求，服务器进程响应该请求。通常，一个服务器进程会同时为多个客户端进程服务。如下图所示，服务器进程 B1 同时为客户进程 A1、A2 和 B2 提供服务。Socket 概述定义：所谓 Socket 通常也称作“套接字”，用于描述 IP 地址和端口，是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。端点：Socket 是连接运行在网络上的两个程序间的双向通信端点。本质：网络通讯本质上就是 Socket 间的通讯。传输：通讯的两端都有 Socket，数据在两个 Socket 之间通过 I/O 来进行传输。套接字（Socket）的类型流式套接字（SOCK_STREAM）提供面向连接、可靠的数据传输服务。数据无差错、无重复地发送，且按发送顺序接收。基于 TCP 协议。数据报式套接字（SOCK_DGRAM）提供无连接服务。数据包以独立包形式发送，不提供无错保证，数据可能丢失，并且接收顺序可能混乱。基于 UDP 协议。原始套接字（SOCK_RAW）允许直接访问底层协议（如 IP、ICMP 等），通常用于网络监控或协议开发。Socket 的设计哲学Socket 起源于 Unix，而 Unix/Linux 基本哲学之一就是“一切皆文件”，都可以用“打开（open）→ 读写（write/read）→ 关闭（close）”模式来操作。Socket 就是该模式的一个实现：Socket 即是一种特殊的文件，一些 Socket 函数就是对其进行的操作（读/写 I/O、打开、关闭）。说白了，Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层，它是一组接口。在设计模式中，Socket 其实就是一个门面模式（Facade Pattern），它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面。对用户来说，一组简单的接口就是全部，让 Socket 去组织数据，以符合指定的协议。随着 Unix 的应用推广，套接字被引进了 Windows 等操作系统。套接字通常只与同一区域的套接字交换数据，Windows Socket 只支持一个通信区域：网际域（AF_INET），这个域被使用网际协议簇的通信进程使用。网络通信流程服务器程序将一个套接字绑定到一个特定的端口，并通过此套接字等待和监听客户的连接请求。客户程序根据服务器程序所在的主机和端口号发出连接请求。如果一切正常，服务器接受连接请求，并获得一个新的绑定到不同端口地址的套接字。客户和服务器通过读、写套接字进行通讯。客户机/服务器（C/S）模式在 TCP/IP 网络应用中，通信的两个进程间相互作用的主要模式是客户机/服务器模式（Client/Server）。即客户向服务器提出请求，服务器接收到请求后，提供相应的服务。服务器端流程：启动：服务器方要先启动，打开一个通信通道并告知本机，它愿意在某一地址和端口上接收客户请求。等待：等待客户请求到达该端口。处理：接收服务请求，处理该客户请求。服务完成后，关闭此新进程与客户的通信链路，并终止。循环：返回第二步，等待另一个客户请求。关闭：关闭服务器。客户端流程：连接：打开一个通信通道，并连接到服务器所在的主机特定的端口。交互：向服务器发送请求，等待并接收应答，继续提出请求。终止：请求结束后关闭通信信道并终止。基于 TCP 协议的 Socket 编程流式传输（客户端）：socket() 函数；bind() 函数可有可无，加上指定传输端口，不加随机分配端口；connect() 函数，填写服务端的地址与端口【网络间通信 AF_INET】；send() 函数；recv() 函数。流式传输（服务端）：socket() 函数；bind() 函数，必须加上指定传输端口【网络间通信 AF_INET】；listen() 函数，使用 iSocketFD；accept() 函数，生成新的 fd，new_fd；send() 函数，new_fd；recv() 函数，new_fd。服务器端先初始化 Socket，然后与端口绑定（bind），对端口进行监听（listen），调用 accept 阻塞，等待客户端连接。在这时如果有个客户端初始化一个 Socket，然后连接服务器（connect），如果连接成功，这时客户端与服务器端的连接就建立了。客户端发送数据请求，服务器端接收请求并处理请求，然后把回应数据发送给客户端，客户端读取数据，最后关闭连接，一次交互结束。基于 TCP 的 Socket 编程实现（SOCK_STREAM）网络间通信 AF_INET，典型的 TCP/IP 四层模型的通信过程。服务器端代码：#include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #define PORT 23 // 端口号 #define BACKLOG 5 // 最大监听数 int main() { int iSocketFD = 0; // socket 句柄 int iRecvLen = 0; // 接收成功后的返回值 int new_fd = 0; // 建立连接后的句柄 char buf[4096] = {0}; // 接收缓冲区 struct sockaddr_in stLocalAddr = {0}; // 本地地址信息结构体 struct sockaddr_in stRemoteAddr = {0}; // 对方地址信息 socklen_t socklen = 0; // 建立 socket iSocketFD = socket(AF_INET, SOCK_STREAM, 0); if(0 > iSocketFD) { printf("创建 socket 失败！\n"); return 0; } stLocalAddr.sin_family = AF_INET; // 该属性表示接收本机或其他机器传输 stLocalAddr.sin_port = htons(PORT); // 端口号 stLocalAddr.sin_addr.s_addr = htonl(INADDR_ANY); // IP，括号内容表示本机 IP // 绑定地址结构体和 socket if(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr))) { printf("绑定失败！\n"); return 0; } // 开启监听，第二个参数是最大监听数 if(0 > listen(iSocketFD, BACKLOG)) { printf("监听失败！\n"); return 0; } printf("iSocketFD: %d\n", iSocketFD); // 在这里阻塞直到接收到消息，参数分别是 socket 句柄，接收到的地址信息以及大小 new_fd = accept(iSocketFD, (void *)&stRemoteAddr, &socklen); if(0 > new_fd) { printf("接收失败！\n"); return 0; } else { printf("接收成功！\n"); // 发送内容，参数分别是连接句柄，内容，大小，其他信息（设为 0 即可） send(new_fd, "这是服务器接收成功后发回的信息!", sizeof("这是服务器接收成功后发回的信息!"), 0); } printf("new_fd: %d\n", new_fd); iRecvLen = recv(new_fd, buf, sizeof(buf), 0); if(0 >= iRecvLen) // 对端关闭连接 返回 0 { printf("接收失败或者对端关闭连接！\n"); } else { printf("buf: %s\n", buf); } close(new_fd); close(iSocketFD); return 0; }客户端代码：#include <stdio.h> #include <string.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #define PORT 23 // 目标地址端口号 #define ADDR "192.168.1.230" // 目标地址 IP int main() { int iSocketFD = 0; // socket 句柄 unsigned int iRemoteAddr = 0; struct sockaddr_in stRemoteAddr = {0}; // 对端，即目标地址信息 socklen_t socklen = 0; char buf[4096] = {0}; // 存储接收到的数据 // 建立 socket iSocketFD = socket(AF_INET, SOCK_STREAM, 0); if(0 > iSocketFD) { printf("创建 socket 失败！\n"); return 0; } stRemoteAddr.sin_family = AF_INET; stRemoteAddr.sin_port = htons(PORT); inet_pton(AF_INET, ADDR, &iRemoteAddr); stRemoteAddr.sin_addr.s_addr = iRemoteAddr; // 连接方法：传入句柄，目标地址，和大小 if(0 > connect(iSocketFD, (void *)&stRemoteAddr, sizeof(stRemoteAddr))) { printf("连接失败！\n"); // printf("connect failed:%d", errno); // 失败时也可打印 errno } else { printf("连接成功！\n"); // 将接收数据打入 buf，参数分别是句柄，储存处，最大长度，其他信息（设为 0 即可） recv(iSocketFD, buf, sizeof(buf), 0); printf("Received:%s\n", buf); } close(iSocketFD); // 关闭 socket return 0; }测试步骤：编译服务器、客户端代码：[root@localhost tcp_socket]# make socket_server_tcp cc socket_server_tcp.c -o socket_server_tcp [root@localhost tcp_socket]# make socket_client_tcp cc socket_client_tcp.c -o socket_client_tcp [root@localhost tcp_socket]# 服务器端口监听：[root@localhost tcp_socket]# ./socket_server_tcp 执行客户端：非 telnet 方式：服务器端显示： [root@localhost tcp_socket]# ./socket_server_tcp iSocketFD: 3 接收成功！ new_fd: 4 接收失败或者对端关闭连接！ 客户端显示： [root@localhost tcp_socket]# ./socket_client_tcp 连接成功！ Received:这是服务器接收成功后发回的信息!telnet 服务器方式：服务器端显示： [root@localhost tcp_socket]# ./socket_server_tcp iSocketFD: 3 接收成功！ new_fd: 4 buf: !"'# 客户端显示： [root@localhost tcp_socket]# telnet 192.168.1.230 Trying 192.168.1.230... Connected to 192.168.1.230. Escape character is '^]'. 这是服务器接收成功后发回的信息!Connection closed by foreign host.基于 UDP 协议的 Socket 编程数据报传输（客户端）：socket() 函数；bind() 函数：绑定客户端的地址与端口【网络间通信 AF_INET】。若服务器端只是收不发数据（即服务端有 recvfrom() 函数无 sendto() 函数），则客户端不需要 bind() 函数；若服务端要发送数据（即有 sendto() 函数），则客户端需要 bind() 函数；客户端 sendto() 函数参数：填写服务端的地址与端口【网络间通信 AF_INET】的结构体地址与结构体长度，服务端必须有 bind() 函数；客户端 recvfrom() 函数参数：NULL，会自动识别。数据报传输（服务端）：socket() 函数；bind() 函数：绑定服务端的地址与端口，以便于后续客户端 sendto() 函数参数的填写。若服务器端只是收不发数据，则客户端不需要 bind() 函数；服务端 sendto() 函数参数：填写客户端的地址与端口的结构体地址与结构体长度，服务端必须有 bind() 函数；recvfrom() 函数参数：NULL，会自动识别。基于 UDP 的 Socket 编程实现（SOCK_DGRAM）网络间通信 AF_INET，典型的 TCP/IP 四层模型的通信过程。服务器端代码（多线程与循环监听）：#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <pthread.h> #include <unistd.h> void * test(void *pvData) { while(1) { sleep(10); printf("################################\n"); } return NULL; } int main(void) { pthread_t stPid = 0; int iRecvLen = 0; int iSocketFD = 0; char acBuf[4096] = {0}; struct sockaddr_in stLocalAddr = {0}; struct sockaddr_in stRemoteAddr = {0}; socklen_t iRemoteAddrLen = 0; /* 创建 socket */ iSocketFD = socket(AF_INET, SOCK_DGRAM, 0); if(iSocketFD < 0) { printf("创建 socket 失败!\n"); return 0; } /* 填写地址 */ stLocalAddr.sin_family = AF_INET; stLocalAddr.sin_port = htons(12345); stLocalAddr.sin_addr.s_addr = 0; /* 绑定地址 */ if(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr))) { printf("绑定地址失败!\n"); close(iSocketFD); return 0; } pthread_create(&stPid, NULL, test, NULL); // 实现了多线程 while(1) // 实现了循环监听 { iRecvLen = recvfrom(iSocketFD, acBuf, sizeof(acBuf), 0, (void *)&stRemoteAddr, &iRemoteAddrLen); printf("iRecvLen: %d\n", iRecvLen); printf("acBuf:%s\n", acBuf); } close(iSocketFD); return 0; }客户端代码：#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> int main(void) { int iRecvLen = 0; int iSocketFD = 0; int iRemotAddr = 0; char acBuf[4096] = {0}; struct sockaddr_in stServerAddr = {0}; // 服务端地址信息 socklen_t iRemoteAddrLen = 0; /* 创建 socket */ iSocketFD = socket(AF_INET, SOCK_DGRAM, 0); if(iSocketFD < 0) { printf("创建 socket 失败!\n"); return 0; } /* 填写服务端地址 */ stServerAddr.sin_family = AF_INET; stServerAddr.sin_port = htons(12345); inet_pton(AF_INET, "192.168.1.230", (void *)&iRemotAddr); stServerAddr.sin_addr.s_addr = iRemotAddr; iRecvLen = sendto(iSocketFD, "这是一个测试字符串", strlen("这是一个测试字符串"), 0, (void *)&stServerAddr, sizeof(stServerAddr)); close(iSocketFD); return 0; }测试步骤：编译服务器： 因为有多线程，所以服务器端进程要进行 pthread 编译：[root@localhost udp_socket]# gcc socket_server_UDP.c -pthread -g -o socket_server_UDP服务器监听：[root@localhost udp_socket]# ./socket_server_UDP 客户端连接服务器：[root@localhost tcp_socket]# ./socket_client_UDP 服务器端口显示结果：[root@localhost udp_socket]# ./socket_server_UDP iSocketFD: 3 ################################ ################################ iRecvLen: 27 acBuf:这是一个测试字符串 iSocketFD: 3 ################################结果解释说明：服务器端有主线程和辅线程。主线程负责打印客户端发送的请求；辅线程每隔 10 秒钟打印一排 # 号。参考链接参考链接：https://blog.csdn.net/zhang___yong/article/details/78702559Java 实现：https://www.cnblogs.com/wzy330782/p/5479833.html基于 TCP/IP 和 UDP 协议的 socket 编程结构解析：https://blog.csdn.net/zhengnice/article/details/51428080说明： 本文示例代码基于 Linux/Unix 环境（使用 POSIX API，如 pthread、arpa/inet.h 等）。若在 Windows 环境下开发，需使用 Winsock 库（winsock2.h）并调整部分函数调用（如 close 改为 closesocket，初始化 WSAStartup 等）。