CS61C项目笔记(一)-Lab1-编译调试初体验
C的编译和运行
在本实验中,我们将使用命令行程序 gcc 在 C 语言中编译程序。运行 gcc 的最简单方法如下:
1 | $ gcc program.c |
这会将 program.c 编译为名为 a.out 的可执行文件。如果你学过 CS61B 或有 Java 经验,你可以把 gcc 看作是 Java 的 C 等价物。可以使用以下命令运行此文件:
1 | $ ./a.out |
可执行文件是 a.out
,那么到底 ./
是干什么用的呢?答:当您要执行可执行文件时,您需要在文件路径前面加上一个文件路径,以便将您的命令与 python3 等命令区分开来。该点表示“当前目录”。顺便说一句,双点 ( ..
) 表示更高一级的目录。
gcc
有各种命令行选项,鼓励您探索。但是,在本实验中,我们将仅使用 -o
,它用于指定 gcc
创建的可执行文件的名称。您可以使用以下命令编译 program.c
为名为 program 的程序,然后运行它。如果您不希望所有可执行文件都命名 a.out
,这将很有帮助。
1 | $ gcc -o program program.c |
若编译的程序需要调试,加上-g指令
C的调试器——gdb
在本练习中,您会发现 GDB 参考卡很有用。GDB 代表“GNU De-Bugger”。 hello.c
使用 -g
标志编译:
1 | $ gcc -g -o hello hello.c |
这会导致 gcc 将信息存储在可执行程序中,以便 gdb
理解它。现在启动调试器 (c)gdb:
1 | $ cgdb hello |
学习这些命令将对本实验的其余部分以及您的 C 编程生涯很有用。在名为 gdb.txt
的文本文件中,回答以下问题。
- 当您处于 gdb 会话中时,如何设置在程序运行时将传递给程序的参数?
- set args arg1 arg2
- 如何创建断点?
- break
- break
- 在断点处停止后,如何在程序中执行下一行 C 代码?
- next or n
- 如果下一行代码是函数调用,则使用对 #3 的答案,则将立即执行整个函数调用。(如果没有,请考虑对 #3 使用不同的命令!你如何告诉GDB你想调试函数内部的代码(即单步执行函数)?(如果您将答案更改为 #3,那么该答案现在很可能适用于此处。
- step or s
- 在断点停止后如何继续程序?
- continue or c
- 如何在 gdb 中打印变量(甚至是像 1+2 这样的表达式)的值?
- ptint
- print 1+2
- ptint
- 如何配置 gdb 以便它在每个步骤后显示变量的值?
- display variable_name
- 如何在当前函数中显示所有变量及其值的列表?
- info locals
- 你如何退出 gdb?
- quit or q
重定向——使用包含你输入的文本文件来运行
1 | ./a.out < fileName.txt |
Valgrind’ing工具
Valgrind 是一个模拟您的 CPU 并跟踪您的内存访问的程序。这会减慢您正在运行的进程(例如,这就是为什么我们并不总是在 Valgrind 中运行所有可执行文件的原因),但也可能会暴露可能仅在一组特殊情况下显示可见的错误行为的错误。
1 | $ valgrind ./segfault_ex |
这应该会导致 Valgrind 输出发生非法访问的位置。将这些结果与您通过打开文件确定的结果进行比较。Valgrind 如何帮助您解决未来的段错误?现在尝试在 no_segfault_ex
上运行 Valgrind。程序不应该崩溃,但文件仍然存在问题。Valgrind可以帮助我们找到(看似看不见的)问题。
不幸的是,在这里您会看到 Valgrind 似乎无法告诉您问题的确切位置。使用 Valgrind 提供的消息来确定哪个变量具有未定义的行为,然后尝试推断一定发生了什么(提示:什么是未初始化的值?
At this point we do not expect you to be familiar with sizeof
(that comes next week!) so all we want 在这一点上,我们不希望您熟悉 sizeof
(下周!),因此我们希望您从本节中得到的只是关于问题可能发生位置的一些直觉。
希望在完成此示例后,您将能够理解并回答以下问题:
- 为什么 Valgrind 很重要,它有什么用?
- 你如何在 Valgrind 中运行程序?
- 您如何解释错误消息?不要害怕他们。尽力而为,向我们寻求帮助。
- 为什么未初始化的变量会导致“heisenbugs”?
我们现在向您介绍 Valgrind,因为它是一个非常重要的工具,一旦您开始编写 C,您就会想要它。然而,要真正理解它,我们需要了解 C 的内存模型,我们将在下周完成。在讲座中介绍完记忆后,回到这个实验并尝试回答以下问题:
- 为什么
no_segfault_ex
程序没有段错误? - 为什么
no_segfault_ex
会产生不一致的输出? - 为什么不正确
sizeof
?你怎么能仍然使用sizeof
,但要使代码正确?