如何通过exec*系列函数向子进程传递整型变量?
咱们直接拆解你遇到的问题:你想通过exec*系列函数给子进程传一个整型变量,但子进程拿到的值完全不对,对吧?先看看代码里的问题出在哪,再给你几个靠谱的解决办法。
为什么你的代码会出错?
首先得明确:exec*系列函数的所有参数都必须是以'\0'结尾的C风格字符串,这是它的设计要求。
你用union把int转成char数组tv.c,但这个数组里并没有自动添加'\0'结尾。比如你设置tv.i=12345,假设是小端字节序,对应的char数组是0x39 0x30 0x00 0x00——第三个字节就是'\0',所以execlp会把tv.c当成字符串"90"来传递,而不是完整的4字节数据。
到了子进程这边,你用memcpy(tv.c, av[0], sizeof(int)),但av[0]的实际长度只有2字节,硬读4字节就会越界,把内存里的随机数据读进来,结果自然就是那个奇怪的1627402297了。另外你还犯了一个小错误:execlp的第一个参数是程序路径,第二个参数应该是子进程的argv[0](通常是程序名),你直接传了tv.c,这也会导致argv的结构混乱。
几种正确的解决方法
方法1:把整型转成字符串传递(最推荐)
既然exec*就是用来传字符串的,那咱们就顺着它的设计来,把int转成字符串传过去,子进程再转回来。
父进程修改后代码:
/* parent */ #include <unistd.h> #include <stdio.h> int main(int ac, char *av[]) { int num = 12345; char num_str[20]; // 把int转成字符串,确保有'\0'结尾 snprintf(num_str, sizeof(num_str), "%d", num); if(fork() == 0) { // 注意:execlp的参数顺序是:程序路径 → argv[0] → 自定义参数 → 空指针 execlp("./test", "./test", num_str, "abcd", (char *)0); } return 0; }
子进程修改后代码:
/* child */ #include <stdio.h> #include <stdlib.h> int main(int ac, char *av[]) { // 把字符串转回int int num = atoi(av[1]); printf("child: ac=%d, num: %d, av1: %s\n", ac, num, av[2]); return 0; }
运行后输出就是你想要的:child: ac=3, num: 12345, av1: abcd。
方法2:用管道传递二进制数据(适合复杂二进制结构)
如果一定要传递原始的二进制int(比如不想做字符串转换),可以用管道来实现——毕竟exec*的参数不适合传非字符串的二进制数据。
父进程代码:
/* parent */ #include <unistd.h> #include <stdio.h> int main(int ac, char *av[]) { int pipefd[2]; // 创建管道,pipefd[0]是读端,pipefd[1]是写端 pipe(pipefd); int num = 12345; if(fork() == 0) { close(pipefd[1]); // 子进程不需要写,关闭写端 // 把管道读端重定向到标准输入,这样子进程可以从stdin读数据 dup2(pipefd[0], STDIN_FILENO); execlp("./test", "./test", "abcd", (char *)0); } else { close(pipefd[0]); // 父进程不需要读,关闭读端 // 把int写入管道 write(pipefd[1], &num, sizeof(num)); close(pipefd[1]); } return 0; }
子进程代码:
/* child */ #include <unistd.h> #include <stdio.h> int main(int ac, char *av[]) { int num; // 从标准输入读入二进制int read(STDIN_FILENO, &num, sizeof(num)); printf("child: ac=%d, num: %d, av0: %s\n", ac, num, av[1]); return 0; }
这种方式直接传递二进制数据,没有字符串转换的开销,还能扩展传递结构体之类的复杂数据。
方法3:用环境变量传递(适合配置类参数)
你也可以把整型转成字符串后放到环境变量里,子进程读取环境变量再转回来,这样参数和业务数据可以分开。
父进程代码:
/* parent */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int ac, char *av[]) { int num = 12345; char env_var[20]; snprintf(env_var, sizeof(env_var), "MY_NUM=%d", num); if(fork() == 0) { // 设置环境变量 putenv(env_var); execlp("./test", "./test", "abcd", (char *)0); } return 0; }
子进程代码:
/* child */ #include <stdlib.h> #include <stdio.h> int main(int ac, char *av[]) { // 读取环境变量 char *num_str = getenv("MY_NUM"); // 判空后转成int,默认值设为-1 int num = num_str ? atoi(num_str) : -1; printf("child: ac=%d, num: %d, av0: %s\n", ac, num, av[1]); return 0; }
最后再提醒一句
永远不要用exec*的参数传递非字符串的二进制数据,因为二进制数据里大概率会包含'\0',会导致参数被截断,或者子进程读取时越界,出现各种不可预测的bug。老老实实用字符串、管道或者环境变量,才是正确的姿势。
内容的提问来源于stack exchange,提问作者umbreLLaJYL

