【Valgrind入门攻略】从内存问题到线程安全
Valgrind是一个十分强大的程序动态分析东西,能够协助咱们探测程序中的各种问题,包括内存管理问题、线程不安全问题等等。本文将介绍Valgrind的基本用法,让你能够快速上手运用。
内存检查
Valgrind有许多功用,其间最著名的就是memcheck
,它能够用来探测常见的内存问题,例如:
- 越界访问
- 运用未初始化的变量
- 不正确开释内存,例如
double-freeing
- 内存走漏
下面,咱们来看一个比如:
#include <stdlib.h>
void f(void) {
int* x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void) {
f();
return 0;
}
咱们能够用Valgrind来分析这段代码:
-
gcc -g membase.c -o membase
,运用-g
选项来携带debug信息 valgrind --leak-check=full ./membase
执行后会得到以下输出:
==7069== Memcheck, a memory error detector
==7069== ... // 省略部分输出
==7069== Invalid write of size 4
==7069== at 0x401144: f (membase.c:5)
==7069== by 0x401155: main (membase.c:9)
==7069== Address 0x4a47068 is 0 bytes after a block of size 40 alloc'd
==7069== at 0x484386F: malloc (vg_replace_malloc.c:393)
==7069== by 0x401137: f (membase.c:4)
==7069== by 0x401155: main (membase.c:9)
==7069== ... // 省略部分输出
==7069== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==7069== at 0x484386F: malloc (vg_replace_malloc.c:393)
==7069== by 0x401137: f (membase.c:4)
==7069== by 0x401155: main (membase.c:9)
==7069== by 0x401155: main (membase.c:9)
==7069== ... // 省略部分输出
咱们能够看到,Valgrind给出了一些警告信息,提示咱们代码中的问题。其间:
==7069== Invalid write of size 4
==7069== at 0x401144: f (membase.c:5)
==7069== by 0x401155: main (membase.c:9)
这部分告知咱们,在不允许的内存区域写入了数据。
==7069== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
这部分告知咱们,动态分配的空间没有被开释,导致内存走漏。
下面,咱们再看一个比如:
#include <stdlib.h>
int main() {
int p, t;
if (p == 5)
t = p + 1;
return 0;
}
咱们能够用Valgrind来分析这段代码:
bashCopy code
valgrind --leak-check=full ./memuninitvar
执行后会得到以下输出:
......
==7274== Conditional jump or move depends on uninitialised value(s)
==7274== at 0x40110E: main (memuninitvar.c:7)
==7274==
......
这部分告知咱们,运用了未初始化的变量。
竞赛条件检查
除了内存问题,Valgrind还能够协助咱们检测线程安全问题。其间,最常用的东西是Helgrind
,它能够用来检测线程竞赛,例如:
- 错误运用POSIX pthreads API
- 潜在的死锁问题
- 数据竞赛:在没有取得锁的情况下访问数据
下面,咱们来看一个比如:
#include <pthread.h>
int var = 0;
void* child_fn ( void* arg ) {
var++;
return NULL;
}
int main ( void ) {
pthread_t child;
pthread_create(&child, NULL, child_fn, NULL);
var++;
pthread_join(child, NULL);
return 0;
}
咱们能够用Valgrind来分析这段代码:
-
gcc -pthread -g helbase.c -o helbase
,运用-pthread
选项来编译多线程程序 valgrind --tool=helgrind ./helbase
执行后会得到以下输出:
......
==7507==
==7507== Possible data race during read of size 4 at 0x404030 by thread #1
==7507== Locks held: none
==7507== at 0x401177: main (helbase.c:13)
==7507==
==7507== This conflicts with a previous write of size 4 by thread #2
==7507== ----------------------------------------------------------------
==7507==
==7507== Possible data race during write of size 4 at 0x404030 by thread #1
==7507== Locks held: none
==7507== at 0x401180: main (helbase.c:13)
==7507==
==7507== This conflicts with a previous write of size 4 by thread #2
......
咱们能够看到,Valgrind给出了一些警告信息,提示咱们代码中的问题。其间:
==7507== Possible data race during read of size 4 at 0x404030 by thread #1
==7507== Locks held: none
==7507== at 0x401177: main (helbase.c:13)
==7507==
==7507== This conflicts with a previous write of size 4 by thread #2
这部分告知咱们,在没有加锁的情况下,两个线程一起读写了同一变量。
为了解决这个问题,咱们能够在代码中添加锁,例如
#include <pthread.h>
pthread_mutex_t mutex; // 声明一个互斥锁
int var = 0;
void* child_fn ( void* arg ) {
pthread_mutex_lock(&mutex);
var++;
pthread_mutex_unlock(&mutex);
return NULL;
}
int main ( void ) {
pthread_t child;
pthread_create(&child, NULL, child_fn, NULL);
pthread_mutex_lock(&mutex);
var++;
pthread_mutex_unlock(&mutex);
pthread_join(child, NULL);
return 0;
}
在子线程和主线程访问共享变量var
之前,先加锁。这样,就能够确保线程安全了。
总结
本文介绍了Valgrind东西的基本用法,包括内存检查和竞赛条件检查。Valgrind是一个十分有用的东西,能够协助咱们发现程序中的一些难以察觉的问题,从而进步代码的质量和可靠性。虽然Valgrind有许多高档用法,但是本文只是对其根底用法进行了介绍,希望能对读者有所启示。