Excel与C语言如何协同工作?

99ANYc3cd6
预计阅读时长 18 分钟
位置: 首页 C语言 正文

execlexec 家族函数中的一个成员,是 Linux/Unix 系统编程中非常重要的一组函数,它的核心作用是:用一个新的程序完全替换当前进程的映像

execl c语言
(图片来源网络,侵删)

execl 是什么?为什么需要它?

想象一下你在 shell 中运行一个程序:

$ ls -l

ls 进程执行完毕后,它就结束了,shell 进程会重新出现,等待你的下一个命令。

如果你写一个 C 程序,想在程序内部执行另一个程序,该怎么办?你写了一个 my_shell.c,它想运行 ls -l,这时,execl 就派上用场了。

execl 不会创建一个新进程,它是在当前进程的上下文中进行操作的,它会:

execl c语言
(图片来源网络,侵删)
  1. 加载一个新的程序(ls)到内存中。
  2. 替换当前进程的代码、数据和堆栈。
  3. 丢弃当前进程的上下文(除了进程 ID、父进程 ID 等内核保留的信息)。
  4. 从新程序的 main 函数开始执行。

一个非常重要的特性execl 函数只有在执行失败时才会返回,如果它成功执行,当前进程就变成了新程序,并且永远不会返回到调用 execl 的代码之后,如果执行失败(找不到文件),它会返回 -1,并且当前进程不会被替换,可以继续执行后续代码。


函数原型和参数

execl 的原型定义在 <unistd.h> 头文件中:

#include <unistd.h>
extern char **environ; // 环境变量列表
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);

参数详解:

  1. const char *path (路径)

    execl c语言
    (图片来源网络,侵删)
    • 这是一个字符串,表示你想要执行的新程序的完整路径
    • "/bin/ls""/usr/bin/gcc"
    • 如果程序在你的系统的 PATH 环境变量中,你也可以只写程序名,"ls",但这依赖于系统配置,使用完整路径更可靠。
  2. const char *arg0, ... (参数列表)

    • 这是一个可变参数列表,用于传递给新程序的命令行参数。
    • arg0:这是传递给新程序的第一个参数,按照惯例,arg0 应该是新程序本身的名称(与 path 中的文件名部分相同),这和新程序在 main 函数中接收到的 argv[0] 是一样的。
    • arg1, arg2, ...:这些是传递给新程序的后续参数,对应 main 函数中的 argv[1], argv[2], ...。
    • 列表的结尾:这个可变参数列表必须以一个 NULL 指针作为结束,这是编译器知道参数列表结束的标志。

execl vs. execv 家族

execlexec 家族的一员,理解它们的区别很重要:

函数名 参数形式 描述
execl l (list) 参数以列表形式逐个传入。
execv v (vector) 参数作为一个字符串数组(char *argv[])传入。
execle l (list), e (environment) execl 一样,但允许你传递自定义的环境变量列表。
execve v (vector), e (environment) execv 一样,但允许你传递自定义的环境变量列表。
execlp l (list), p (path) execl 一样,但如果 path 不包含 ,会使用 PATH 环境变量来搜索程序。
execvp v (vector), p (path) execv 一样,但如果 path 不包含 ,会使用 PATH 环境变量来搜索程序。
  • p (path): 表示会自动在系统环境变量 PATH 中查找程序。
  • l (list): 表示参数是逐个列出的。
  • v (vector): 表示参数是一个字符串数组。
  • e (environment): 表示可以传递自定义的环境变量。

execl 的适用场景:当你要传递的参数数量很少且固定时,execl 非常方便,因为它语法简单。


代码示例

这是一个非常经典的例子:在 C 程序中调用 ls -l 命令。

// my_ls.c
#include <stdio.h>
#include <unistd.h> // 包含 execl 的头文件
#include <stdlib.h> // 包含 exit 的头文件
int main() {
    printf("Hello from my_ls.c! Now I will execute 'ls -l'.\n");
    // 调用 execl 函数
    // 参数解释:
    // "/bin/ls": 要执行的程序路径
    // "ls": 传递给 ls 程序的 argv[0]
    // "-l": 传递给 ls 程序的 argv[1]
    // NULL: 参数列表的结束标志
    execl("/bin/ls", "ls", "-l", NULL);
    // 注意:execl 执行成功,下面的代码将永远不会被执行到。
    // 因为当前进程已经被 ls 程序完全替换了。
    // execl 失败(找不到 /bin/ls),它才会返回 -1。
    // 我们需要检查返回值。
    perror("execl failed"); // execl 失败,打印错误信息
    exit(EXIT_FAILURE);    // 退出当前进程
    // 下面的代码也永远不会被执行
    printf("This line will never be reached if execl succeeds.\n");
    return 0;
}

编译和运行:

$ gcc my_ls.c -o my_ls
$ ./my_ls

预期输出:

Hello from my_ls.c! Now I will execute 'ls -l'.
total 16
-rwxr-xr-x 1 user group 8560 Oct 26 10:30 my_ls
-rw-r--r-- 1 user group  512 Oct 26 10:31 my_ls.c

你会看到 ls -l 命令的输出,而不是 my_ls 程序后续的打印语句,这完美地展示了 execl 的替换作用。


execlfork 的结合使用

我们不希望父进程被新程序替换,我们希望父进程能够创建一个子进程来运行新程序,而父进程本身继续执行。

这时,execl 就需要和 fork() 一起使用。fork() 创建一个子进程的副本,然后在子进程中调用 execl 来加载新程序。

示例:在子进程中运行 ls

// run_ls_in_child.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> // 包含 wait 的头文件
int main() {
    pid_t pid = fork(); // 创建一个子进程
    if (pid < 0) {
        // fork 失败
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 这是子进程
        printf("Child process (PID: %d) is executing 'ls -l'.\n", getpid());
        execl("/bin/ls", "ls", "-l", NULL);
        // execl 失败,子进程会执行到这里
        perror("execl failed in child");
        exit(EXIT_FAILURE);
    } else {
        // 这是父进程
        printf("Parent process (PID: %d) created child process (PID: %d).\n", getpid(), pid);
        // 父进程等待子进程结束
        int status;
        wait(&status); // 阻塞,直到子进程终止
        printf("Parent process: Child has finished.\n");
    }
    return 0;
}

编译和运行:

$ gcc run_ls_in_child.c -o run_ls_in_child
$ ./run_ls_in_child

预期输出:

Parent process (PID: 1234) created child process (PID: 1235).
Child process (PID: 1235) is executing 'ls -l'.
total 16
-rwxr-xr-x 1 user group 8560 Oct 26 10:30 my_ls
-rw-r--r-- 1 user group  512 Oct 26 10:31 my_ls.c
Parent process: Child has finished.

在这个例子中,父进程 (run_ls_in_child) 成功创建了子进程,子进程执行了 ls 命令后退出,然后父进程恢复执行并打印了最后一条消息,这才是最常见的使用模式。


关键点总结

  1. 替换而非创建execl 不创建新进程,它用新程序替换当前进程的映像。
  2. 永不返回(成功时)execl 成功后,控制权永远交给了新程序,调用点之后的代码不会被执行。
  3. 错误检查:必须检查 execl 的返回值,如果返回 -1,说明执行失败,当前进程未被替换,需要处理错误。
  4. 参数格式:参数以列表形式传递,并且必须以 NULL。
  5. arg0 习惯:第一个参数 arg0 最好是程序自身的名称。
  6. fork 结合:在绝大多数情况下,execl 是在 fork() 创建的子进程中调用的,以避免父进程被意外替换。
-- 展开阅读全文 --
头像
dede手机端插件如何安装使用?
« 上一篇 04-19
dede上传图片出错怎么办?
下一篇 » 04-19

相关文章

取消
微信二维码
支付宝二维码