在 C++ 开发中,执行外部程序(如二进制文件、系统命令或 Shell 脚本)是一个常见的需求,尤其是在 Linux 系统中。C++ 提供了多种方式来实现这一功能,其中最常用的是 system() 函数和 exec 函数族。今天,我们一起来唠一唠。


一、system() 函数:简单但有风险的解决方案

system() 是标准 C 库中的一个函数,用于执行系统命令。它通过调用系统的 Shell 来执行传递给它的命令,是一种相对简单的调用方式。尽管方便易用,但也存在一些潜在的安全隐患,特别是在处理不受信任的用户输入时。

1.1 system() 的函数声明

1
int system(const char *command);
  • 参数command 是一个字符串,表示要执行的系统命令。
  • 返回值:如果 Shell 能被正确执行,system() 返回 Shell 进程的返回值。如果出现错误(如没有可用的 Shell),它将返回一个非零值。

1.2 示例

执行一个简单的 Linux 命令,比如列出当前目录下的文件:

1
system("ls -la");

这个调用会在 Shell 中执行 ls -la,并显示结果。

1.3 system() 的优缺点

优点

  • 简单易用system() 非常直观,你只需提供一个字符串作为命令,系统会帮你处理余下的操作。
  • 兼容性:由于 system() 是标准 C 函数,它可以跨平台工作,尽管不同平台上的细节可能会有所不同。

缺点

  • 安全问题system() 的最大问题是安全性,特别是在处理用户输入时。如果用户输入的命令直接传递给 system(),攻击者可能注入恶意代码。例如,用户输入 "; rm -rf /" 就可能会删除整个文件系统。因此,建议不要将用户输入直接传递给 system(),除非做了严格的验证。
  • 性能开销system() 会启动一个新的 Shell 进程来执行命令,这意味着每次调用都有额外的性能开销,特别是在需要频繁执行命令的场景下,这种开销可能会变得显著。

1.4 使用场景

  • system() 适用于一些简单的、非频繁的命令执行任务,例如在程序结束时调用 system("pause") 来暂停终端。
  • 但是,在安全和性能要求较高的场景中,尽量避免使用 system(),而是选择更安全的替代方案。

二、exec 函数族:更灵活和安全的替代方案

system() 不同,exec 函数族提供了更直接、底层的方式来执行外部程序。它们不会创建新的进程,而是用新程序替换当前进程的映像。exec 函数族中的函数会立即将当前进程的代码和数据替换为新执行的程序,除非调用失败,否则它不会返回到原来的程序。

2.1 exec 函数族的常见函数

exec 函数家族有多个变体,最常用的两个是:

  • execl():将程序路径和参数作为变长参数传递。
  • execv():传递程序路径和参数数组。

2.2 execl()execv() 的函数声明

1
2
int execl(const char *path, const char *arg0, ..., NULL);
int execv(const char *path, char *const argv[]);
  • path:要执行的程序的路径。
  • arg0, ... / argv[]:要传递给新程序的参数。execl() 接受一系列变长参数,而 execv() 则接受一个指针数组。

2.3 示例

执行 ls -la 命令,使用 execl()execv() 的等效代码如下:

使用 execl()

1
execl("/bin/ls", "ls", "-la", NULL);

这里 /bin/ls 是要执行的命令路径,"ls" 是命令名,"-la" 是参数,最后一个 NULL 表示参数列表的结束。

使用 execv()

1
2
char *args[] = {"ls", "-la", NULL};
execv("/bin/ls", args);

execv() 的参数通过数组传递,最后一个元素必须为 NULL,以表示参数的结束。

2.4 exec 的返回值

exec 函数只有在执行失败时才返回 -1,并设置 errno 来指示错误原因。如果成功执行,当前进程的代码将被新程序替换,且不会返回。

2.5 exec 的优缺点

优点

  • 灵活性exec 函数族允许程序直接替换当前进程映像,这是 system() 无法做到的。这在需要运行一个长期运行的外部程序而不希望产生新进程时非常有用。
  • 安全性:相较于 system()exec 函数族更加安全,因为它不会经过 Shell 解析命令,减少了命令注入的风险。

缺点

  • 使用复杂exec 函数的调用比 system() 要复杂一些,特别是在处理参数时,需要额外的代码来构造参数数组或变长参数列表。
  • 不会返回:成功调用 exec 后,原来的进程代码段、数据段会被新程序替换,因此调用之后的代码不会被执行。

2.6 使用场景

  • exec 函数族适合在当前进程中直接调用外部程序的场景,特别是在希望替换当前进程并执行新程序时非常有效,例如在服务器程序中 fork 子进程并通过 exec 执行新的任务。
  • 它常用于需要更细粒度控制外部程序执行的场合,如编写守护进程、任务调度系统等。

三、system()exec 的对比

特性 system() exec()
进程控制 启动一个新的 Shell 进程 替换当前进程
使用复杂度 简单,只需传递命令字符串 较复杂,需要处理参数
安全性 存在命令注入风险 更安全,不经过 Shell 解析
返回值 命令执行的返回值 仅在执行失败时返回
创建新进程 否,替换当前进程
参数传递 字符串 参数数组或变长参数
性能 需要创建新进程,开销较大 替换当前进程,开销较小

四、总结

在 C++ 开发中,选择 system() 还是 exec 函数族取决于具体的应用场景:

  • 如果你需要执行简单的外部命令,并且不太关注性能和安全问题,system() 是一个方便的选择。但要确保命令来源安全,避免命令注入攻击。
  • 如果你需要更灵活和安全的控制,或者希望替换当前进程执行外部程序,exec 函数族则是更好的选择。