第十三章:调试

第十三章:调试

13.1 调试的基本概念

调试的作用

调试的主要功能:

  1. 定位错误
  2. 分析问题
  3. 验证修复
  4. 性能优化

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// 调试宏
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "DEBUG: " fmt "\n", ##__VA_ARGS__)

int main() {
int x = 10;
int y = 0;

// 使用断言
assert(x > 0);

// 使用调试宏
DEBUG_PRINT("x = %d, y = %d", x, y);

// 检查除零错误
if (y == 0) {
DEBUG_PRINT("Division by zero!");
return 1;
}

int result = x / y;
printf("Result: %d\n", result);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译(启用调试信息)
gcc -g debug_basic.c -o debug_basic

# 运行
./debug_basic

调试工具

常用的调试工具:

  1. GDB
  2. Valgrind
  3. strace
  4. ltrace

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
// 内存分配
char *str = malloc(10);
if (!str) return 1;

// 字符串操作
strcpy(str, "Hello");
printf("String: %s\n", str);

// 内存泄漏
// str = NULL; // 注释掉这行会导致内存泄漏

// 使用已释放的内存
free(str);
// printf("String: %s\n", str); // 注释掉这行会导致段错误

return 0;
}

调试命令:

1
2
3
4
5
6
7
8
9
10
11
# 使用GDB调试
gdb ./debug_tools

# 使用Valgrind检查内存问题
valgrind --leak-check=full ./debug_tools

# 使用strace跟踪系统调用
strace ./debug_tools

# 使用ltrace跟踪库调用
ltrace ./debug_tools

13.2 调试技术

断点调试

断点调试的方法:

  1. 设置断点
  2. 单步执行
  3. 查看变量
  4. 修改变量

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>

int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

int main() {
int n = 5;
int result = factorial(n);
printf("Factorial of %d is %d\n", n, result);
return 0;
}

GDB调试命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动GDB
gdb ./breakpoint

# 设置断点
(gdb) break factorial
(gdb) break main

# 运行程序
(gdb) run

# 单步执行
(gdb) next
(gdb) step

# 查看变量
(gdb) print n
(gdb) print result

# 继续执行
(gdb) continue

内存调试

内存调试的方法:

  1. 内存泄漏检测
  2. 越界访问检测
  3. 使用已释放内存检测
  4. 内存对齐检查

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
// 内存泄漏
char *leak = malloc(10);
strcpy(leak, "leak");

// 越界访问
char *overflow = malloc(5);
strcpy(overflow, "overflow");

// 使用已释放的内存
char *use_after_free = malloc(10);
strcpy(use_after_free, "test");
free(use_after_free);
printf("%s\n", use_after_free);

// 内存对齐问题
char *unaligned = malloc(1);
int *ptr = (int *)(unaligned + 1);
*ptr = 42;

return 0;
}

Valgrind调试命令:

1
2
3
4
5
6
7
8
# 检查内存泄漏
valgrind --leak-check=full ./memory_debug

# 检查内存错误
valgrind --tool=memcheck ./memory_debug

# 检查堆使用情况
valgrind --tool=massif ./memory_debug

13.3 性能调试

性能分析

性能分析的方法:

  1. 时间分析
  2. 内存分析
  3. CPU分析
  4. I/O分析

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 1000000

void slow_function() {
int *arr = malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i++) {
arr[i] = i;
}
free(arr);
}

void fast_function() {
int *arr = malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i += 16) {
arr[i] = i;
}
free(arr);
}

int main() {
clock_t start, end;

// 测试慢函数
start = clock();
slow_function();
end = clock();
printf("Slow function time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);

// 测试快函数
start = clock();
fast_function();
end = clock();
printf("Fast function time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);

return 0;
}

性能分析命令:

1
2
3
4
5
6
7
8
9
10
11
# 使用gprof分析
gcc -pg performance.c -o performance
./performance
gprof performance gmon.out

# 使用perf分析
perf record ./performance
perf report

# 使用time命令
time ./performance

性能优化

性能优化的方法:

  1. 算法优化
  2. 内存优化
  3. 缓存优化
  4. 并行优化

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 未优化的版本
void unoptimized_copy(char *dst, const char *src, size_t n) {
for (size_t i = 0; i < n; i++) {
dst[i] = src[i];
}
}

// 优化的版本
void optimized_copy(char *dst, const char *src, size_t n) {
// 使用内存对齐
size_t i = 0;
while (i < n && ((uintptr_t)(dst + i) & 7) != 0) {
dst[i] = src[i];
i++;
}

// 使用64位复制
while (i + 8 <= n) {
*(uint64_t *)(dst + i) = *(const uint64_t *)(src + i);
i += 8;
}

// 处理剩余字节
while (i < n) {
dst[i] = src[i];
i++;
}
}

int main() {
const size_t size = 1024 * 1024;
char *src = malloc(size);
char *dst1 = malloc(size);
char *dst2 = malloc(size);

// 初始化源数据
memset(src, 'A', size);

// 测试未优化的版本
unoptimized_copy(dst1, src, size);

// 测试优化的版本
optimized_copy(dst2, src, size);

free(src);
free(dst1);
free(dst2);

return 0;
}

实践练习

  1. 基本调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建测试程序
cat > debug_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = NULL;
*ptr = 42; // 段错误
return 0;
}
EOL

# 编译和调试
gcc -g debug_test.c -o debug_test
gdb ./debug_test
  1. 内存调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建内存测试程序
cat > memory_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
char *ptr = malloc(10);
ptr[10] = 'A'; // 越界访问
free(ptr);
return 0;
}
EOL

# 编译和检查
gcc memory_test.c -o memory_test
valgrind ./memory_test
  1. 性能调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建性能测试程序
cat > perf_test.c << EOL
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
clock_t start = clock();

// 执行一些操作
int *arr = malloc(1000000 * sizeof(int));
for (int i = 0; i < 1000000; i++) {
arr[i] = i;
}
free(arr);

clock_t end = clock();
printf("Time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
EOL

# 编译和分析
gcc -pg perf_test.c -o perf_test
./perf_test
gprof perf_test gmon.out

思考题

  1. 调试的主要功能是什么?
  2. 常用的调试工具有哪些?
  3. 如何进行内存调试?
  4. 性能调试的方法有哪些?
  5. 如何优化程序性能?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. GDB 文档
  3. Valgrind 文档
  4. System V ABI
  5. ELF 文件格式规范