C语言多线程读写共享内存整数异常:读值为-1或段错误
问题分析与修复方案
看起来你在多线程共享内存读写的逻辑里踩了几个关键的坑,导致段错误和读取值异常的问题,咱们一步步拆解清楚:
核心问题梳理
- 共享内存创建缺失
O_CREAT参数:原多线程代码中shm_open没加O_CREAT,如果是第一次运行,共享内存对象不存在的话,fd会返回-1,后续mmap会失败拿到无效地址,这是段错误的主要诱因之一。 - 混淆字符串写入与二进制整数写入:你用
sprintf把整数转成ASCII字符串写入共享内存,但读取时却试图把它当成二进制整数解析(*(int*)memory)——这两种方式的内存布局完全不同:sprintf(memory, "%d", i)会把i转成字符串,比如i=10时,内存里是'1' '0' '\0'(占3字节)- 而你真正需要的是直接写入整数的二进制值,就像单线程代码里的
memcpy那样
- 错误的内存读取方式:
(int)memory是把共享内存的地址转成整数,不是读取内存里存储的值,这就是你读到-1(无效地址的整数表示)的原因。 - 退出条件逻辑错误:
if((int)memory == 0)是判断共享内存地址是否为NULL,不是判断内存里存储的整数是否为0,导致读线程永远无法退出。
修正后的多线程代码
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <string.h> struct args { void* memptr; sem_t* semptr; }; void *p1(void *vargp) { int* memory = (int*)((struct args*)vargp)->memptr; // 直接转成int*,简化读写逻辑 sem_t* semaphore = ((struct args*)vargp)->semptr; for(int i=1; i<=10; i++) { sem_wait(semaphore); // 先获取信号量,保证互斥访问 printf("写入线程: 写入值 %d\n", i); *memory = i; // 直接写入二进制整数,效果等同于memcpy sem_post(semaphore); // 释放信号量 sleep(1); } // 写入结束标记0 sem_wait(semaphore); *memory = 0; printf("写入线程: 写入结束标记0\n"); sem_post(semaphore); sleep(0.1); return NULL; // 线程函数必须返回void*类型值 } void *p2(void *vargp) { int* memory = (int*)((struct args*)vargp)->memptr; sem_t* semaphore = ((struct args*)vargp)->semptr; sleep(0.1); // 等待写入线程先启动 while(1) { sem_wait(semaphore); int val = *memory; // 读取内存里的二进制整数 printf("读取线程: 读取到值 %d\n", val); if(val == 0){ sem_post(semaphore); break; // 读到结束标记,退出循环 } sem_post(semaphore); sleep(1); } return NULL; } const int ByteSize = sizeof(int); // 用sizeof(int)替代硬编码4,适配不同平台 const char* SharedName = "memNameTest"; const char* SemaphoreName = "semNameTest"; int main() { // 添加O_CREAT参数,确保共享内存不存在时自动创建 int fd = shm_open(SharedName, O_CREAT | O_RDWR, 0644); if(fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } ftruncate(fd, ByteSize); void* memptr = mmap(0, ByteSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(memptr == MAP_FAILED) { perror("mmap failed"); close(fd); exit(EXIT_FAILURE); } // 信号量添加O_CREAT参数,初始值设为1(允许第一个线程先获取) sem_t* semptr = sem_open(SemaphoreName, O_CREAT, 0644, 1); if(semptr == SEM_FAILED) { perror("sem_open failed"); munmap(memptr, ByteSize); close(fd); exit(EXIT_FAILURE); } struct args *Share = (struct args *)malloc(sizeof(struct args)); Share->memptr = memptr; Share->semptr = semptr; pthread_t thread1, thread2; printf("启动线程...\n"); pthread_create(&thread1, NULL, p1, (void*)Share); pthread_create(&thread2, NULL, p2, (void*)Share); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("线程执行完毕\n"); // 彻底清理资源,避免残留 munmap(memptr, ByteSize); close(fd); sem_close(semptr); shm_unlink(SharedName); // 用shm_unlink删除共享内存对象 sem_unlink(SemaphoreName); // 删除信号量对象 free(Share); // 释放malloc分配的结构体 return 0; }
关键修改点说明
- 共享内存创建:给
shm_open加上O_CREAT参数,同时增加错误检查,避免无效文件描述符导致后续操作崩溃。 - 内存读写优化:把共享内存指针直接转成
int*,通过*memory = i直接写入二进制整数,读取时直接取*memory,和单线程的memcpy效果一致且更简洁。 - 信号量调整:将信号量初始值设为1(保证任意一个线程能先获取锁),同时添加
O_CREAT和错误检查。 - 退出条件修正:读取内存里的实际值
val == 0来判断是否退出,而非判断共享内存地址。 - 资源完善清理:添加
shm_unlink和sem_unlink彻底删除共享内存与信号量对象,避免系统残留;同时释放malloc分配的结构体。 - 线程函数规范:给线程函数添加
return NULL,符合void*返回值的要求。
为什么单线程能正常运行?
单线程代码里用了memcpy(memptr, &i, sizeof(int))直接写入整数的二进制值,读取时用*((int*)memptr)正确解析二进制内容,逻辑完全匹配,所以能正常工作。而多线程里误用了sprintf(字符串写入),导致内存内容和读取方式不匹配,再加上共享内存创建的问题,才出现了段错误。
内容的提问来源于stack exchange,提问作者Mojtaba AlKhalifah
相关产品推荐
相关产品推荐

