第一题
问题
首先,编写一个名为 null.c 的简单程序,它创建一个指向整数的指针,将其设置为NULL,然后尝试对其进行释放内存操作。把它编译成一个名为 null 的可执行文件。当你运行这个程序时会发生什么?
自己写的
输出如下:
无任何输出或错误提示。
第二题
问题
接下来,编译该程序,其中包含符号信息(使用-g 标志)。这样做可以将更多信息放入可执行文件中,使调试器可以访问有关变量名称等的更多有用信息。通过输入 gdb null,在调试器下运行该程序,然后,一旦 gdb 运行,输入 run。gdb 显示什么信息?
补充:
-
gcc 的
-g
标志gcc 的
-g
标志用于在编译时生成调试信息,这些调试信息可以帮助在程序崩溃或出现错误时使用调试器(如 gdb)进行调试。使用-g
标志时,编译器会将符号信息和源代码行号等调试信息嵌入到可执行文件中,这样你就可以更方便地跟踪程序的执行情况。使用方法:
在使用 gcc 编译源代码时,加入
-g
标志即可:gcc -g -o output_program source_file.c
-
-g 会在编译过程中将调试信息添加到可执行文件中。
-
-o output_program 是指定输出文件的名称。
-
source_file.c 是你的源代码文件。
编译完成后,你可以使用调试器(如 gdb)进行调试:
gdb ./output_program
调试时,调试器会使用
-g
标志生成的调试信息,显示源代码、变量值等调试信息,帮助定位问题。 -
-
gcc 的
-o
标志(小写的o)gcc 的
-o
标志用于指定输出文件的名称。默认情况下,gcc 编译器将生成一个名为a.out
的可执行文件,但使用-o
标志可以自定义输出文件的名称。语法:
gcc [源文件] -o [输出文件名]
-
gcc 的
-O
标志(大写的O)gcc 的
-O
标志用于启用优化,目的是通过编译器优化代码来提高程序的运行效率。-O 后面可以跟不同的数字,以表示不同级别的优化。优化级别的选择影响编译过程的时间、生成的代码的执行效率以及可执行文件的大小。-O
标志的使用:-
-O0
(默认值):不进行优化。这是编译器的默认行为,适用于调试阶段,编译速度快,生成的代码没有经过优化,便于调试。
gcc -O0 source_file.c -o output_program
-
-O1
:进行基本优化。启用一些基本的优化,提升程序的性能,同时不会显著增加编译时间。适用于一般情况下的优化。
gcc -O1 source_file.c -o output_program
-
-O2
:进行更高级的优化。启用更强的优化手段(例如删除不必要的代码、优化循环等),可以显著提高程序的运行效率,编译时间会相对较长。
gcc -O2 source_file.c -o output_program
-
-O3
:进行最大化优化。启用所有 -O2 的优化,并进一步进行更多的优化,目的是提高程序的性能,适用于对性能要求极高的程序。编译时间和生成的代码大小都会增加。
gcc -O3 source_file.c -o output_program
-
-Os
:优化代码大小。进行优化以减少程序的大小,而不是最大化执行速度。这在内存受限的环境中非常有用(例如嵌入式系统)。
gcc -Os source_file.c -o output_program
-
-Ofast
:进行快速优化。启用所有的 -O3 优化,并且还启用一些可能不符合标准的优化,进一步提高程序的执行速度。
gcc -Ofast source_file.c -o output_program
总结:
-
-O0
:不进行优化,适合调试。 -
-O1
:基本优化,编译速度较快。 -
-O2
:更强的优化,适合大多数应用。 -
-O3
:最大化优化,提升性能但增加编译时间和文件大小。 -
-Os
:优化程序大小,适用于内存限制的环境。 -
-Ofast
:最大化性能优化,适合对速度要求极高的情况。
-
注意
在编译的时候一定不要写成这样子:
gcc -o -g null_once null.c
否则会报如下错误:
第三题
问题
最后,对这个程序使用 valgrind 工具。我们将使用属于 valgrind 的 memcheck 工具来分析发生的情况。输入以下命令来运行程序:valgrind --leak-check=yes null。当你运行它时会发生什么?你能解释工具的输出吗?
补充
如果是在 centos 下安装 valgrind 可以看这篇教程 valgrind安装及使用
然后重中之重,安装的时候在root用户下安装,避免权限问题!!!
第四题
问题
编写一个使用 malloc()来分配内存的简单程序,但在退出之前忘记释放它。这个程序运行时会发生什么?你可以用 gdb 来查找它的任何问题吗?用 valgrind 呢(再次使用
--leak-check=yes 标志)?
使用 gdb:
没看出问题
使用 valgrind:
检测出了内存泄漏问题。
第五题
问题
编写一个程序,使用 malloc 创建一个名为 data、大小为 100 的整数数组。然后,将data[100]设置为 0。当你运行这个程序时会发生什么?当你使用 valgrind 运行这个程序时会发生什么?程序是否正确?
使用 gdb:
没看出问题
使用 valgrind:
检测出了无效写入错误。
第六题
问题
创建一个分配整数数组的程序(如上所述),释放它们,然后尝试打印数组中某个元素的值。程序会运行吗?当你使用 valgrind 时会发生什么?
输出了数组中指定的元素的值
使用 valgrind:
检测出了三个无效读取
第七题
问题
现在传递一个有趣的值来释放(例如,在上面分配的数组中间的一个指针)。会发生什么?你是否需要工具来找到这种类型的问题?
直接报错,不需要工具就能找到这个问题。
补充
为什么会报错?
free(p + 20)
报错的原因是 free 只能释放 malloc 分配的原始指针,而不能释放偏移后的指针。
第八题
问题
尝试一些其他接口来分配内存。例如,创建一个简单的向量似的数据结构,以及使用 realloc()来管理向量的相关函数。使用数组来存储向量元素。当用户在向量中添加条目时,请使用realloc()为其分配本多空间。这样的向量表现如何?它与链表相比如何?使用valgrind来帮助你发现错误。
这就是类似C++中vector的数据结构。 特性类似数组,可以实现O(1)的访问或者尾部新增,但是如果空间满了需要realloc的时候需要O(n)的时间。