codeblocks 怎么写编译选项_C语言:GDB调试时遇到宏定义怎么办?一个小技巧帮你一秒钟搞定

引言

最近更新了几篇关于编译器、性能优化等相对比较底层的文章,有童鞋发私信给我,希望能写几篇介绍程序调试技巧的文章。

想了下,就从C语言的宏定义入手吧。这个是调试C语言程序时经常会遇到的问题,在我刚入门时也曾经困惑过我。

5ef8279781a06d2eccdd4cd4959effd5.png

关于C语言的宏

C语言,易于入门,难以精通。这样说,我想应该不会有人反对吧?

它的易,在于其精简的语法,简单的数据类型。它的难,在于它的灵活。

说起它的难,大家第一反应肯定是指针。作为C语言的精髓,指针的存在,使得C语言足够灵活,异常强大。但同时,它也使得C语言变得难以掌控,程序变得难以理解。

5032c36b2bf02aa8d93d8954ac46af6a.png

那么,除了指针呢?还有宏。

C语言中,宏是和指针一样强大的存在。毫不夸张的说,通过C语言的宏定义,甚至可以发展出一门全新的编程语言。

恰当的使用宏,能够使得代码更加简洁精炼,易读,还能提高程序的运行效率。真正的C语言高手,都非常善于用宏来实现一些高级的编程技巧。

Linux内核就是一个典型的案例。作为全球顶尖黑客大牛们通力合作的产物,Linux内核把C语言的宏发挥到了极致,各种精妙、有趣的宏技巧在Linux内核代码中随处可见。

7506606def6835e144a14eacf7f4bd7d.png

但是,和指针一样,宏也是一把双刃剑。大量的运用宏,尤其是一些高级、精妙的技巧,会使得代码变得难以理解,对于理解代码的实现细节和调试带来不小的难度。对于这点,我想研究过Linux内核代码的童鞋都深有体会。

GDB调试时遇到宏怎么办?

我们知道,在C语言程序预编译阶段,所有的宏定义都会被展开在C源码文件中引用宏的地方。因此,一般编译过后的目标文件中,不存在关于宏定义的任何信息。如下面这段代码:

880b6250fe1be7f2e60fbf93e29d7140.png

先编译一下:

gcc -g test.c -o test

然后用GDB调试:

4a9740b0cc8bbd6391fa0ba40fc14ec3.png

尽管我们编译时加了“-g”选项,但当我们在GDB中查看MONDAY的值和使用MAX宏时,仍然会提示当前上行文中找不到这两个符号。

怎么解决呢?其实GCC已经给我们提供了解决方案。

GCC的调试选项 -g

我们知道,要想用GDB进行调试,必须在用GCC编译时加上“-g”选项。但很多童鞋可能不知道的是,和优化选项“-Ox”一样,调试选项“-g”也有几个等级可选:

  • -g 默认选项,同-g2
  • -g0 不生成任何调试信息,和编译时不加“-g”是一样的。
  • -g1 生成最少量的调试信息,这些信息足够用来通过backtrace查看调用栈符号信息。主要包括了函数声明、外部变量和行号等信息,但是不包含局部变量信息。这个选项比较少用。
  • -g2 生成足够多的调试信息,可以用GDB进行正常的程序调试,这个是默认选项。
  • -g3 在-g2的基础上产生更多的信息,如宏定义。

可见,我们编译时加的“-g”选项,其实等同于“-g2”,它产生了足够多的调试信息,我们可以用gdb查看调用栈、查看局部变量等。但是,要想查看宏定义,则必须要使用“-g3”选项

使用-g3调试选项

重新编译一下:

gcc -g3 test.c -o test

用gdb重新调试:

7e8a69b74541e3d5c88d21e28890065f.png

用“-g3”选项重新编译之后,就可以在gdb中查看宏定义了。

本来,到这里就该结束了,但是为了满足好奇心,我们看一下“-g2”和“-g3”对目标文件产生了什么影响。

“-g2”和“-g3”对目标文件的影响

我们先看一下“-g2”,重新编译一下,然后用readelf命令查看一下目标文件中的节区头部(section header)表:

gcc -g2 test.c -o testreadelf -S test

主要看一下和调试信息相关的节区:

9c421de1c04edca18fddf71a0b409454.png

-g2 目标文件调试信息

GCC默认使用的调试信息存储格式叫做DWARF,gdb就是基于这个格式的规范,实现的源码级调试。

稍微解释一下:

  • .debug_aranges 地址范围信息
  • .debug_info 最核心的调试信息
  • .debug_abbrev 各编译单元的缩写表
  • .debug_line 行号信息
  • .debug_str 被“.debug_info”节区使用的字符串表

然后,再用“-g3”选项重新编译一下:

gcc -g3 test.c -o testreadelf -S test

再看下节区表:

519c9645e45f925b2d8d8793672562fd.png

-g3 目标文件调试信息

跟“-g2”相比,“-g3”多了一个.debug_macro,这个节区就是主要用来存放宏定义的信息。

我们用下面这个命令来查看一下这个节区里面都存放了哪些信息:

readelf --debug-dump test

输出信息比较多,和“.debug_macro”有关的如下图所示:

4a3575652686a7c1db98d0982c061a3f.png

测试程序中定义的两个宏MONDAY和MAX的信息都包含在这个节区中,包括文件信息、行号信息、以及宏定义具体内容等。

结语

作为程序员,我们每天都在和各种Bug斗智斗勇,程序调试和问题定位的能力,对我们来说是至关重要的一项基本技能。

工欲善其事,必先利其器。

742c66503f552e2a6ff49d48dd7eea5f.png

接下来,我会更新一系列文章,深入讲解程序调试和问题定位的技巧,以及其背后的实现原理。主要内容包括:

  • 调试器的工作原理
  • 调试器常见功能的实现方法,如断点、单步执行、调用栈、源码级调试等
  • 常用工具的高级使用技巧,如GCC、GDB、binutil工具集、sysstat工具集等
  • 在无合适工具可用的特殊环境下,如何自己制作调试工具
7f126eef51c561caefc4f0b904a2b7d9.png

感兴趣的童鞋欢迎欢迎右上角关注!

如果觉得有用的话,别忘了点赞!把知识分享给更多志同道合的人!谢谢!


也欢迎围观我另外一个正在更新中的系列专题文章:

你真的理解"Hello world"吗? 从编译链接到OS内核系列专题(已更新三篇)

以及其他几篇关于性能优化的文章。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!以下是一个使用Code::Blocks编C语言五子棋游戏程序,代码超过了300行。请注意,该程序只是一个简单的演示,还有许多可以改进的地方。 ```c #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <windows.h> #define WIDTH 15 #define HEIGHT 15 #define PLAYER1 'X' #define PLAYER2 'O' #define BLANK ' ' void gotoxy(int x, int y); void clear_screen(); void draw_board(); void init_board(char board[][WIDTH]); void print_board(char board[][WIDTH]); int is_game_over(char board[][WIDTH], int x, int y, char player); int get_direction(int x1, int y1, int x2, int y2); int is_win(char board[][WIDTH], int x, int y); int is_tie(char board[][WIDTH]); int main() { char board[HEIGHT][WIDTH]; int x, y, turn = 1, win = 0; clear_screen(); init_board(board); draw_board(); print_board(board); while (!win && !is_tie(board)) { printf("Player %d's turn (row, col): ", turn); scanf("%d%d", &x, &y); if (x < 1 || x > HEIGHT || y < 1 || y > WIDTH) { printf("Invalid input, please try again.\n"); continue; } x--; y--; if (board[x][y] != BLANK) { printf("There is already a piece at (%d, %d), please try again.\n", x + 1, y + 1); continue; } board[x][y] = (turn == 1 ? PLAYER1 : PLAYER2); clear_screen(); draw_board(); print_board(board); if (is_win(board, x, y)) { printf("Player %d wins!\n", turn); win = 1; } else { turn = (turn == 1 ? 2 : 1); } } if (!win) { printf("It's a tie!\n"); } return 0; } void gotoxy(int x, int y) { COORD c; c.X = x - 1; c.Y = y - 1; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); } void clear_screen() { system("cls"); } void draw_board() { int i, j; for (i = 1; i <= HEIGHT; i++) { for (j = 1; j <= WIDTH; j++) { if (i == 1 && j == 1) { printf("%c", 201); } else if (i == HEIGHT && j == 1) { printf("%c", 200); } else if (i == 1 && j == WIDTH) { printf("%c", 187); } else if (i == HEIGHT && j == WIDTH) { printf("%c", 188); } else if (i == 1 || i == HEIGHT) { printf("%c", 205); } else if (j == 1 || j == WIDTH) { printf("%c", 186); } else { printf(" "); } if (j == WIDTH) { printf("\n"); } } } } void init_board(char board[][WIDTH]) { int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { board[i][j] = BLANK; } } } void print_board(char board[][WIDTH]) { int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { printf("%c ", board[i][j]); } printf("\n"); } } int is_game_over(char board[][WIDTH], int x, int y, char player) { if (is_win(board, x, y)) { return 1; } if (is_tie(board)) { return 1; } return 0; } int get_direction(int x1, int y1, int x2, int y2) { if (x1 == x2) { if (y1 < y2) { return 0; } else if (y1 > y2) { return 4; } } else if (y1 == y2) { if (x1 < x2) { return 2; } else if (x1 > x2) { return 6; } } else if (x1 < x2) { if (y1 < y2) { return 1; } else if (y1 > y2) { return 3; } } else if (x1 > x2) { if (y1 < y2) { return 7; } else if (y1 > y2) { return 5; } } return -1; } int is_win(char board[][WIDTH], int x, int y) { int i, j, k, d, count; for (d = 0; d < 8; d++) { count = 1; for (k = 1; k < 5; k++) { i = x; j = y; switch (d) { case 0: i -= k; break; case 1: i -= k; j += k; break; case 2: j += k; break; case 3: i += k; j += k; break; case 4: i += k; break; case 5: i += k; j -= k; break; case 6: j -= k; break; case 7: i -= k; j -= k; break; } if (i < 0 || i >= HEIGHT || j < 0 || j >= WIDTH || board[i][j] != board[x][y]) { break; } count++; } if (count == 5) { return 1; } } return 0; } int is_tie(char board[][WIDTH]) { int i, j; for (i = 0; i < HEIGHT; i++) { for (j = 0; j < WIDTH; j++) { if (board[i][j] == BLANK) { return 0; } } } return 1; } ``` 这个程序使用二维字符数组来表示棋盘,其空位用空格字符表示,玩家1用字符'X'表示,玩家2用字符'O'表示。在游戏过程,程序会将棋盘绘制到控制台上,并提示玩家输入坐标来下棋。如果玩家输入的坐标无效或已经有棋子了,程序会提示玩家重新输入。如果有一个玩家获胜或棋盘已满,则游戏结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值