下面我将通过一系列从简单到复杂的实例,带你一步步入门 Linux C 语言编程。

环境准备
在开始之前,请确保你的 Linux 系统已经安装了必要的编译工具。
- 打开终端。
- 更新软件包列表 (推荐):
sudo apt update # 对于 Debian/Ubuntu 系统
- 安装 GCC (GNU Compiler Collection) 和 GDB (GNU Debugger):
sudo apt install build-essential gdb
build-essential是一个元数据包,它会自动安装gcc,g++,make等核心编译工具。
Hello, World!
这是所有编程语言的第一个程序,让我们看看它在 Linux 下是如何工作的。
代码 (hello.c)
#include <stdio.h> // 标准输入输出库,包含了 printf 函数的声明
int main() {
// printf 函数用于在标准输出(通常是终端)上打印字符串
// \n 是一个换行符
printf("Hello, Linux World!\n");
// main 函数返回 0 表示程序成功执行完毕
return 0;
}
代码解析
#include <stdio.h>: 这是一个预处理器指令,它告诉编译器在编译之前,将stdio.h文件的内容包含进来。stdio.h定义了标准输入/输出函数,如printf,scanf等。int main(): 这是程序的入口点,操作系统会首先调用这个函数。printf(...): 一个来自stdio.h的函数,用于格式化并输出文本。return 0;:main函数的返回值。0在 Unix/Linux 系统中通常代表程序成功执行,任何非零值都表示程序遇到了错误。
编译与运行
-
保存代码:将上面的代码保存为
hello.c。
(图片来源网络,侵删) -
编译:在终端中,使用
gcc编译器进行编译。gcc hello.c -o hello
gcc: 调用 GCC 编译器。hello.c: 源文件。-o hello: 指定输出的可执行文件名为hello,如果不写-o,默认会生成一个名为a.out的文件。
-
运行:编译成功后,你会得到一个名为
hello的可执行文件,运行它:./hello
- 表示在当前目录下寻找并执行该程序,因为 Linux 的
PATH环境变量通常不包含当前目录,所以必须这样写。
- 表示在当前目录下寻找并执行该程序,因为 Linux 的
输出:
Hello, Linux World!
文件操作
Linux 中“一切皆文件”,文件操作是 C 语言编程的核心,下面是一个读取文件内容的例子。

代码 (read_file.c)
#include <stdio.h>
#include <stdlib.h> // 用于 exit 函数
int main() {
FILE *fp; // 文件指针
char ch; // 用于存储读取的字符
// 以只读模式 ("r") 打开文件 "test.txt"
fp = fopen("test.txt", "r");
// 检查文件是否成功打开
if (fp == NULL) {
perror("Error opening file"); // perror 会打印 "Error opening file: " 并附上系统错误信息
return EXIT_FAILURE; // EXIT_FAILURE 是 <stdlib.h> 中定义的非零错误码
}
// 循环读取文件,直到到达文件末尾 (EOF - End of File)
printf("Content of test.txt:\n");
while ((ch = fgetc(fp)) != EOF) {
putchar(ch); // 将读取到的字符输出到标准输出
}
printf("\n");
// 关闭文件,这是一个好习惯
fclose(fp);
return EXIT_SUCCESS; // EXIT_SUCCESS 是 <stdlib.h> 中定义的 0
}
代码解析
FILE *fp: C 语言使用FILE类型的指针来操作文件流。fopen("test.txt", "r"): 打开文件。- 第一个参数是文件名。
- 第二个参数是打开模式:
"r": 只读。"w": 只写,如果文件不存在则创建,如果存在则清空。"a": 追加,如果文件不存在则创建,如果存在则在末尾写入。"r+": 读写。
if (fp == NULL):fopen可能会失败(例如文件不存在),此时它会返回NULL,必须检查。perror(): 一个非常有用的函数,它打印你提供的自定义错误信息,并附上系统描述的错误原因(如 "No such file or directory")。fgetc(fp): 从文件指针fp指向的文件中读取一个字符。EOF: 一个宏,代表文件结束。fclose(fp): 关闭文件,释放系统资源。
编译与运行
- 创建一个测试文件:
echo "This is a test file for C programming on Linux." > test.txt
- 编译 C 程序:
gcc read_file.c -o read_file
- 运行:
./read_file
输出:
Content of test.txt:
This is a test file for C programming on Linux.
命令行参数处理
很多 Linux 工具都通过命令行参数来配置行为(如 ls -l),C 语言可以通过 main 函数的参数来获取它们。
代码 (args.c)
#include <stdio.h>
int main(int argc, char *argv[]) {
// argc: argument count (参数个数)
// argv: argument vector (参数向量),一个字符串数组
printf("Program name: %s\n", argv[0]); // argv[0] 总是程序的名称
printf("Total arguments: %d\n", argc);
printf("Arguments passed:\n");
// 从 1 开始遍历,因为 argv[0] 是程序名
for (int i = 1; i < argc; i++) {
printf(" argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
代码解析
int argc:argc是一个整数,代表传递给程序的参数总数,包括程序名本身。char *argv[]:argv是一个字符指针数组,每个元素都是一个指向参数字符串的指针。argv[0]指向程序的可执行文件名。argv[1]指向第一个实际参数。argv[argc-1]指向最后一个参数。argv[argc]是一个NULL指针,标志着数组的结束。
编译与运行
- 编译:
gcc args.c -o args
- 运行:尝试传入不同的参数。
./args ./args hello world ./args -l -a --verbose
输出示例 (./args hello world):
Program name: ./args
Total arguments: 3
Arguments passed:
argv[1] = hello
argv[2] = world
使用系统调用
这是 Linux C 编程最强大也最核心的部分,系统调用是用户程序请求内核服务的接口,我们通常不直接调用系统调用,而是使用 C 标准库(如 glibc)提供的封装函数,因为它们更安全、更方便。
下面这个例子使用 fork() 和 exec() 系列函数来创建一个子进程,并让子进程执行一个外部命令(ls -l),而父进程则等待子进程结束。
代码 (process_example.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // fork, exec, pid_t 等函数的头文件
#include <sys/wait.h> // waitpid 函数的头文件
#include <string.h>
int main() {
pid_t pid; // 进程ID类型
int status;
printf("Parent process (PID: %d) is running...\n", getpid());
// fork() 创建一个子进程
// 如果成功,在父进程中返回子进程的PID,在子进程中返回 0
// 如果失败,在父进程中返回 -1
pid = fork();
if (pid < 0) {
// fork 失败
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// --- 子进程代码块 ---
printf("Child process (PID: %d) created. Executing 'ls -l'...\n", getpid());
// execlp 会用 "ls -l" 命令替换当前子进程的映像
// 执行成功后,execlp 不会返回
// 如果失败,才会返回 -1
execlp("ls", "ls", "-l", NULL);
// 如果执行到这里,说明 execlp 失败了
perror("execlp failed");
exit(EXIT_FAILURE);
} else {
// --- 父进程代码块 ---
printf("Parent process (PID: %d) is waiting for child (PID: %d) to finish...\n", getpid(), pid);
// 父进程等待子进程结束
// waitpid 会阻塞,直到指定的子进程状态改变
// -1 表示等待任意子进程
waitpid(pid, &status, 0);
// 检查子进程的退出状态
if (WIFEXITED(status)) {
printf("Child process (PID: %d) exited with status: %d\n", pid, WEXITSTATUS(status));
}
printf("Parent process (PID: %d) finished.\n", getpid());
}
return 0;
}
代码解析
#include <unistd.h>: 包含了 POSIX 标准接口,如fork,exec,pipe等。#include <sys/wait.h>: 包含了waitpid等与进程等待相关的函数。pid_t pid:pid_t是一个用于进程 ID 的数据类型。fork(): 这是 Linux/Unix 中创建新进程的唯一方法,它会复制当前进程,创建一个几乎完全相同的子进程。if (pid == 0): 这个if-else结构是fork的标准用法。pid == 0的分支在子进程中运行。execlp(...):exec系列函数用于执行一个新程序,它会替换当前进程的映像,也就是说,子进程在调用execlp后,就不再是原来的 C 程序了,而是变成了ls命令。p后缀表示会在PATH环境变量中搜索程序。- 参数列表:
execlp("要执行的程序", "argv[0]", "argv[1]", ..., NULL),最后一个参数必须是NULL。
waitpid(pid, &status, 0): 父进程调用它来挂起自己的执行,直到子进程终止。WIFEXITED(status)和WEXITSTATUS(status): 宏,用于从waitpid获取的status中解析子进程的退出状态。
编译与运行
- 编译:
gcc process_example.c -o process_example
- 运行:
./process_example
输出:
你会看到 ls -l 命令的输出结果,穿插在父进程和子进程的打印信息中,具体输出会根据你当前目录的文件而变化。
Parent process (PID: 1234) is running...
Child process (PID: 1235) created. Executing 'ls -l'...
total 16
drwxr-xr-x 2 user user 4096 Oct 26 10:30 .
drwxr-xr-x 3 user user 4096 Oct 26 10:29 ..
-rwxr-xr-x 1 user user 8608 Oct 26 10:30 process_example
-rw-r--r-- 1 user user 123 Oct 26 10:30 process_example.c
Parent process (PID: 1234) is waiting for child (PID: 1235) to finish...
Child process (PID: 1235) exited with status: 0
Parent process (PID: 1234) finished.
总结与进阶
- 使用 GDB 调试: 当程序出错时,使用 GDB 是必不可少的。
gdb ./your_program
在 GDB 中,你可以设置断点 (
break main)、单步执行 (next,step)、查看变量 (print var_name) 等。 - Makefile: 当项目变大,有多个源文件时,手动编译会变得很麻烦。
Makefile和make工具可以自动化编译过程。 - 多线程: 使用
<pthread.h>库可以编写多线程程序,充分利用多核 CPU。 - 网络编程: 使用
<sys/socket.h>等头文件可以进行 Socket 编程,开发客户端/服务器应用。
这些实例涵盖了 Linux C 编程的基础,从这些起点出发,你可以探索更广阔的领域,如系统编程、驱动开发、内核模块等,Linux 为 C 语言程序员提供了一个强大而灵活的平台。
