共享内存传递char数组参数异常:execv传参错误原因及修复方案
问题原因分析
你的问题核心出在错误地将字符串指针(char*)直接赋值给单个char类型的共享内存元素,以及对共享内存传递字符串的逻辑理解有误:
- 指针赋值的致命错误:在子进程中,
shmc[0] = argv[1];这行代码是把argv[1]这个指针(64位系统下是8字节的地址值)强行塞进1字节的char变量里,直接截断了指针的高位字节,只保留低8位数值,完全不是你想要的字符串内容。同理shmc[1] = argv[2];和shmc[2] = argv[3];也犯了同样的错误。 - 跨进程指针的无效性:就算你能完整存储指针,子进程的地址空间在
fork()后是独立的(写时复制机制),子进程里的argv指针在父进程的地址空间中是无效的,父进程根本无法通过这个指针访问到子进程地址空间里的字符串。 - 参数传递的逻辑错误:父进程里的
&shmp[1]是取共享内存中第2个char元素的地址,而这个元素里存的是argv[2]指针的低8位数值,当你把这个地址传给execv时,程序会把这个地址开始的内存当作字符串读取,自然会出现乱码或者不符合预期的结果(比如你看到的23和3)。
解决方法
正确的做法是将字符串的实际内容复制到共享内存中,而不是存储指针。具体实现步骤如下:
1. 计算共享内存所需大小
需要存储三个字符串,每个字符串都要包含终止符\0,所以总大小应该是三个字符串的长度之和加上3(每个字符串的终止符)。我们可以动态计算这个大小,避免浪费内存。
2. 修改子进程代码:复制字符串到共享内存
使用strcpy将argv[1]、argv[2]、argv[3]的内容依次复制到共享内存中,每个字符串以\0结尾,确保父进程能正确识别每个字符串的边界。
3. 修改父进程代码:从共享内存中提取字符串地址
父进程挂载共享内存后,依次找到每个字符串的起始地址(第一个字符串从shmp开始,第二个从第一个字符串的结束位置+1开始,以此类推),然后将这些地址传入execv。
修改后的完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char *argv[]) { // 检查输入参数是否足够 if (argc < 4) { fprintf(stderr, "Usage: %s str1 str2 str3\n", argv[0]); exit(EXIT_FAILURE); } int shmid; char *shm_addr; pid_t pid; // 计算需要的共享内存大小:三个字符串的长度+3个终止符 size_t shm_size = strlen(argv[1]) + 1 + strlen(argv[2]) + 1 + strlen(argv[3]) + 1; shmid = shmget(IPC_PRIVATE, shm_size, 0666 | IPC_CREAT); if (shmid == -1) { perror("shmget failed"); exit(EXIT_FAILURE); } pid = fork(); if (pid == -1) { perror("fork failed"); shmctl(shmid, IPC_RMID, NULL); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程:挂载共享内存并复制字符串 shm_addr = (char *)shmat(shmid, NULL, 0); if (shm_addr == (char *)-1) { perror("shmat failed in child"); exit(EXIT_FAILURE); } // 依次复制三个字符串到共享内存 char *current = shm_addr; strcpy(current, argv[1]); current += strlen(current) + 1; strcpy(current, argv[2]); current += strlen(current) + 1; strcpy(current, argv[3]); // 卸载共享内存 if (shmdt(shm_addr) == -1) { perror("shmdt failed in child"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程:等待子进程完成,然后读取共享内存并调用execv wait(NULL); shm_addr = (char *)shmat(shmid, NULL, 0); if (shm_addr == (char *)-1) { perror("shmat failed in parent"); shmctl(shmid, IPC_RMID, NULL); exit(EXIT_FAILURE); } // 提取三个字符串的地址 char *str1 = shm_addr; char *str2 = str1 + strlen(str1) + 1; char *str3 = str2 + strlen(str2) + 1; // 准备execv的参数数组 char *arg_vec[] = {"./test", str2, str3, NULL}; execv("./test", arg_vec); // 如果execv执行到这里,说明出错了 perror("execv failed"); shmdt(shm_addr); shmctl(shmid, IPC_RMID, NULL); exit(EXIT_FAILURE); } }
额外注意事项
- 一定要添加错误检查:
shmget、shmat、fork、execv等系统调用都可能失败,需要通过返回值判断并处理错误,避免隐藏问题。 - 共享内存的清理:父进程中,
execv成功后会替换当前进程的地址空间,所以shmdt和shmctl不会被执行,你可以考虑在./test程序中清理共享内存,或者使用信号、文件锁等机制来确保共享内存被正确释放。 - 如果字符串长度不确定,也可以在共享内存的开头存储每个字符串的长度,这样父进程可以更方便地定位每个字符串的位置。
内容的提问来源于stack exchange,提问作者user13851309
相关产品推荐
相关产品推荐

