未用-fsanitize=address编译时程序触发SEGFAULT,如何修复?
问题描述
我正在完成学校作业,编写一个基于pthreads的C项目。发现未使用-fsanitize=address编译时,运行程序会触发段错误,错误信息为[1] 2446 segmentation fault ./time;使用该选项编译后,程序会输出time(2544,0x10b1d1600) malloc: nano zone abandoned due to inability to preallocate reserved vm space.的提示,但仍能正常运行。
环境信息
- 操作系统:macOS 12.6
- 编译器版本:
Apple clang version 14.0.0 (clang-1400.0.29.102) Target: x86_64-apple-darwin21.6.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin
项目Makefile
CC = /usr/bin/gcc CPPFLAGS =-g -Wall -fsanitize=address -std=c11 HDRS = gui2.h display.h OBJS = gui2.o display.o main: $(OBJS) $(HDRS) $(CC) $(CPPFLAGS) -lpthread -o time time.c $(OBJS) %.o: %.c %.h .PHONY: clean clean: rm -r time.dSYM && rm $(OBJS) time
项目代码
time.c
#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <time.h> #include <unistd.h> #include "gui2.h" typedef struct data { int tid; pthread_mutex_t *mutex; pthread_cond_t *cond; } data; typedef struct gui_data { int bin; int *done; pthread_mutex_t *mutex; pthread_cond_t *cond; } gui_data; void *thread_function(void *args) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; pthread_t t; pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_cond_t cond; pthread_cond_init(&cond, NULL); data *thread_data = (data*)args; gui_data *threadData = (gui_data*)malloc(sizeof(gui_data)); threadData->bin = thread_data->tid; threadData->done = (int*)malloc(sizeof(int)); *(threadData->done) = 0; threadData->mutex = &mutex; threadData->cond = &cond; // 用线程内的显式变量实现计数机制,而非外部变量 for (int i = 0; i < 2; ++i) { *(threadData->done) = 0; pthread_mutex_lock(&mutex); pthread_create(&t, NULL, gui, threadData); if (threadData->done == 0) { pthread_cond_wait(threadData->cond, threadData->mutex); } nanosleep(&ts, NULL); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); } pthread_join(t,NULL); free(threadData->done); free(threadData); pthread_exit(NULL); } int main() { int n = 100; pthread_t tid[n]; data args[n]; for (int i = 0; i < n; i++) { args[i].tid = i; } struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; for (int i = 0; i < n; ++i) { nanosleep(&ts, NULL); pthread_create(&tid[i], NULL, thread_function, &args[i]); } return EXIT_SUCCESS; }
gui2.c
#include <stdlib.h> #include <stdio.h> #include <math.h> #include <time.h> #include <pthread.h> #include "display.h" #define PI2 6.28318530717 typedef struct gui_data { int bin; int *done; pthread_mutex_t *mutex; pthread_cond_t *cond; } gui_data; void *gui(void *count) { gui_data *guiData = (gui_data*)count; pthread_mutex_lock(guiData->mutex); int x, y; char panel[HEIGHT][WIDTH]; fill(panel); x = ((guiData->bin / 10) % 10); y = (guiData->bin % 10); set(panel, x, y, 'X'); system("clear"); display(panel); printf("\n"); *(guiData->done) = 1; pthread_mutex_unlock(guiData->mutex); pthread_exit(NULL); }
gui2.h
#ifndef GUI_H #define GUI_H void *gui(void *count); #endif
display.c
#include <stdio.h> #include <string.h> #include "display.h" void set(char buf[HEIGHT][WIDTH], int i, int j, char c) { buf[j][i] = c; } void fill(char buf[HEIGHT][WIDTH]) { for (int j = 0; j < HEIGHT; j++) { for (int i = 0; i < WIDTH; i++) { buf[j][i] = '`'; } } } void display(char buf[HEIGHT][WIDTH]) { char frame[HEIGHT*(WIDTH+1)+1]; copy_frame(frame, buf); printf("%s", frame); } void copy_frame(char f[], char b[HEIGHT][WIDTH]) { for (int i = 0; i < HEIGHT; i++) { strncpy( &(f[i*(WIDTH+1)]), b[i], WIDTH ); f[i*(WIDTH+1) + WIDTH] = '\n'; } f[HEIGHT*(WIDTH+1)] = '\0'; }
display.h
#ifndef HEADER_DISPLAY #define HEADER_DISPLAY #define WIDTH 10 #define HEIGHT 10 void set(char buf[HEIGHT][WIDTH], int, int, char); void fill(char buf[HEIGHT][WIDTH]); void display(char buf[HEIGHT][WIDTH]); void copy_frame(char[], char buf[HEIGHT][WIDTH]); #endif
问题分析与修复方案
1. 核心段错误原因:指针与值的错误比较
在thread_function中,这段代码是致命错误:
if (threadData->done == 0)
threadData->done是指向int的指针,此处错误地将指针本身与0(空指针)比较,而实际需要检查的是它指向的变量值是否为0。正确写法应为:
if (*(threadData->done) == 0)
原写法会导致条件永远为真(因为done指针已通过malloc分配,不为空),线程进入pthread_cond_wait后,后续的信号可能无法正确触发,加上循环内线程资源未正确回收,最终引发段错误。
2. 线程资源泄漏与重复创建问题
thread_function的for循环中,每次迭代都会复用同一个pthread_t t变量创建新线程,但仅在循环结束后join一次,导致前序创建的gui线程资源泄漏,且无法正确回收。
修复方法:将pthread_t t放入循环内,每次创建后及时join:
for (int i = 0; i < 2; ++i) { *(threadData->done) = 0; pthread_mutex_lock(&mutex); pthread_t t; // 每次循环创建新的线程ID变量 pthread_create(&t, NULL, gui, threadData); if (*(threadData->done) == 0) { pthread_cond_wait(threadData->cond, threadData->mutex); } nanosleep(&ts, NULL); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); pthread_join(t, NULL); // 及时回收当前gui线程 }
3. main函数未等待子线程结束
main函数创建100个thread_function线程后直接返回,会导致进程提前终止,子线程资源无法正确释放。需在main末尾加入循环,等待所有子线程结束:
// 创建子线程的循环保持不变 for (int i = 0; i < n; ++i) { nanosleep(&ts, NULL); pthread_create(&tid[i], NULL, thread_function, &args[i]); } // 新增:等待所有子线程完成 for (int i = 0; i < n; ++i) { pthread_join(tid[i], NULL); } return EXIT_SUCCESS;
4. malloc提示的处理
malloc: nano zone abandoned...是macOS下AddressSanitizer的常见提示,与系统内存分配机制相关,不影响程序功能。修复上述核心问题后,该提示会自然减少或消失,无需额外处理。
5. 优化内存管理
可以去掉done的动态分配,直接将其作为gui_data的成员,减少malloc/free的开销与风险:
// 修改gui_data结构体 typedef struct gui_data { int bin; int done; // 直接用int成员,替代指针 pthread_mutex_t *mutex; pthread_cond_t *cond; } gui_data;
对应修改相关代码:
thread_function中:threadData->done = 0;、if (threadData->done == 0)gui函数中:guiData->done = 1;
同时删除free(threadData->done);语句。
6. 销毁同步对象
在thread_function末尾,销毁初始化的mutex和cond,避免资源泄漏:
pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond);
修复后的关键代码示例
修改后的thread_function
void *thread_function(void *args) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_cond_t cond; pthread_cond_init(&cond, NULL); data *thread_data = (data*)args; gui_data *threadData = (gui_data*)malloc(sizeof(gui_data)); threadData->bin = thread_data->tid; threadData->done = 0; // 使用int成员替代动态分配 threadData->mutex = &mutex; threadData->cond = &cond; for (int i = 0; i < 2; ++i) { threadData->done = 0; pthread_mutex_lock(&mutex); pthread_t t; pthread_create(&t, NULL, gui, threadData); if (threadData->done == 0) { pthread_cond_wait(threadData->cond, threadData->mutex); } nanosleep(&ts, NULL); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); pthread_join(t, NULL); // 及时回收线程 } free(threadData); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); pthread_exit(NULL); }
修改后的gui函数
void *gui(void *count) { gui_data *guiData = (gui_data*)count; pthread_mutex_lock(guiData->mutex); int x, y; char panel[HEIGHT][WIDTH]; fill(panel); x = ((guiData->bin / 10) % 10); y = (guiData->bin % 10); set(panel, x, y, 'X'); system("clear"); display(panel); printf("\n"); guiData->done = 1; // 直接修改成员变量 pthread_mutex_unlock(guiData->mutex); pthread_exit(NULL); }
内容的提问来源于stack exchange,提问作者cptalpdeniz

