Linux c c++ 开发调试技巧
看到一篇介绍 linux c/c++ 开发调试技巧的文章,感觉挺使用,哪来和大家分享。
通向 UNIX 天堂的 10 个阶梯
Author: Arpan Sen, 高级技术人员, Systems Documentation, Inc. (SDI)
讨论几种可以帮助 C++ 开发人员节省时间的技巧和免费工具。
C++ 开发人员在日常工作中通常要完成多个任务:开发新软件、调试其他人的代码、制订测试计划、为每个计划开发测试、管理衰退软件(regression suite)等等。在多种角色之间频繁转换会消耗大量宝贵的时间。为了帮助缓解这个问题,本文提供 10 种能够有效提高生产力的方法。本文中的示例使用 tcsh version 6,但是提供的思想适用于所有 UNIX? shell 变体。本文还介绍可以在 UNIX 平台上使用的几种开放源码工具。
保证数据安全
在 shell 提示上使用 rm -rf * 或其变体可能是导致 UNIX 开发人员丢失数小时工作成果的最常见的原因。有几种方法可以解决这个问题:通过在 $HOME/.aliases 中使用 alias rm、alias cp 或alias mv,把这些命令连接到它们的交互式版本,然后在系统启动时引用这个文件。根据登录 shell的不同,这意味着把 source $HOME/.aliases 放在 .cshrc(对于 C/tcsh shell)或 .profile 或.bash_profile(对于 Bourne shell)中,见清单 1。
清单 1. 为 rm、cp 和 mv 设置别名
alias rm 'rm –i'
alias cp 'cp –i'
alias mv 'mv –i'
对于 tcsh shell 的用户还有另一种方法,在启动脚本中添加以下行:
set rmstar on
在设置了 rmstar 变量的情况下,如果发出 rm * 命令,系统会提示您确认,见清单 2。
清单 2. 在 tcsh 中使用 rmstar shell 变量
arpan@tintin# pwd
/home/arpan/IBM/documents
arpan@tintin# set rmstar on
arpan@tintin# rm *
Do you really want to delete all files? [n/y] n
但是,如果使用带 -f 选项的 rm、cp 或 mv 命令,那么会抑制交互模式。更有效的一种方法是,为这些 UNIX 命令创建自己的版本,并使用 $HOME/.recycle_bin 这样的预定义文件夹保存删除的数据。清单 3 给出一个称为 saferm 的示例脚本,它只接受文件和文件夹名。
清单 3. saferm 脚本
#!/usr/bin/tcsh
if (! -d ~/.recycle_bin) then
mkdir ~/.recycle_bin
endif
mv $1 ~/.recycle_bin/$1.`date +%F`
自动备份数据
恢复数据需要全面的策略和措施。根据需求的不同,数据备份可以在每天夜间执行,也可以每隔几小时执行一次。在默认情况下,应该使用 cron 工具备份 $HOME 及其所有子目录,并把备份保存在预先指定的文件系统区域中。注意,应该只有系统管理员对备份数据有 读(Write) 或 执行(Execute)的权限。下面的 cron 脚本简要演示备份的设置:
0 20 * * * /home/tintin/bin/databackup.csh
这个脚本在每天 20:00 备份数据。数据备份脚本见清单 4。
清单 4. 数据备份脚本
cd /home/tintin/database/src
tar cvzf database-src.tgz.`date +%F` database/ main/ sql-processor/
mv database-src.tgz.`date +%F` ~/.backup/
chmod 000 ~/.backup/database-src.tgz.`date +%F`
另一种策略是在网络中维护一些名称简单明了的文件系统区域,比如/backup_area1、/backup_area2 等等。希望备份数据的开发人员应该在这些区域中创建目录或文件。另外,一定要注意一点:与 /tmp 相似,这种区域必须打开 sticky 位。
浏览源代码
使用可免费下载的 cscope 实用程序(见 参考资料 中的链接)是发现和浏览现有源代码的好方法。cscope 需要一个文件列表(C 或 C++ 头文件、源代码文件、flex 和 bison 文件、内联源代码[.inl] 文件等等),以便创建它自己的数据库。创建数据库之后,它会在一个简洁的界面中列出源代码。清单 5 演示如何构建并调用 cscope 数据库。
清单 5. 使用 cscope 构建并调用源代码数据库
arpan@tintin# find . –name “*.[chyli]*” > cscope.files
arpan@tintin# cscope –b –q –k
arpan@tintin# cscope –d
cscope 的 -b 选项让它创建内部数据库;-q 选项让它创建索引文件以加快搜索;-k 选项让 cscope在搜索时不考虑系统头文件(否则,即使是最简单的搜索,也会产生大量结果)。
使用 -d 选项调用 cscope 界面,见清单 6。
清单 6. cscope 界面
Cscope version 15.5 Press the ? key for help
Find this C symbol:
Find this global definition:
Find functions called by this function:
Find functions calling this function:
Find this text string:
Change this text string:
Find this egrep pattern:
Find this file:
Find files #including this file:
按 Ctrl-D 退出 cscope。使用 Tab 键在数据 cscope 列表和 cscope 选项(例如,Find C Symbol和 Find file)之间切换。清单 7 给出在搜索名称包含 database 的文件时的屏幕快照。按 0、1等分别查看各个文件。
清单 7. 在搜索名为 database 的文件时 cscope 的输出
Cscope version 15.5 Press the ? key for help
File
0 database.cpp
1.database.h
2.databasecomponents.cpp
3.databasecomponents.h
Find this C symbol:
Find this global definition:
Find functions called by this function:
Find functions calling this function:
Find this text string:
Change this text string:
Find this egrep pattern:
Find this file:
Find files #including this file:
用 doxygen 调试遗留代码
要想有效地调试别人开发的代码,就要花时间了解现有软件的总体结构 — 类及其层次结构,全局变量和静态变量,公共接口例程。在从现有的源代码中提取类层次结构方面,GNU 实用程序doxygen(见 参考资料 中的链接)可能是最好的工具。
要想在项目上运行 doxygen,首先需要在 shell 提示上运行 doxygen -g。这个命令在当前工作目录中生成一个名为 Doxyfile 的文件,必须手工编辑此文件。编辑之后,对 Doxyfile 再次运行doxygen。清单 8 给出一个运行示例。
清单 8. 运行 doxygen
arpan@tintin# doxygen -g
arpan@tintin# ls
Doxyfile
… [after editing Doxyfile]
arpan@tintin# doxygen Doxyfile
您需要理解 Doxyfile 中的几个字段。比较重要的字段是:
* OUTPUT_DIRECTORY。保存生成的文档文件的目录。
* INPUT。这是一个以空格分隔的列表,其中包含必须为其生成文档的所有源代码文件和文件夹。
* RECURSIVE。如果源代码列表是层次化的,就把这个字段设置为 YES。这样就不必在 INPUT 中指定所有文件夹,只需在 INPUT 中指定顶层文件夹并把这个字段设置为 YES。
* EXTRACT_ALL。这个字段必须设置为 YES,这告诉 doxygen 应该从没有文档的所有类和函数中提取文档。
* EXTRACT_PRIVATE。这个字段必须设置为 YES,这告诉 doxygen 应该在文档中包含类的私有数据成员。
* FILE_PATTERNS。除非项目没有采用一般的 C 或 C++ 源代码文件扩展名,比如.c、.cpp、.cc、.cxx、.h 或 .hpp,否则不需要在这个字段中添加设置。
注意:Doxyfile 中必须研究的其他字段取决于项目需求和所需的文档细节。清单 9 给出一个示例Doxyfile。
清单 9. 示例 Doxyfile
OUTPUT_DIRECTORY = /home/tintin/database/docs
INPUT = /home/tintin/project/database
FILE_PATTERNS =
RECURSIVE = yes
EXTRACT_ALL = yes
EXTRACT_PRIVATE = yes
EXTRACT_STATIC = yes
使用 STL 和 gdb
在使用 C++ 开发的软件中,最复杂的部分常常使用 C Standard Template Library (STL) 中的类。但糟糕的是,调试包含许多 STL 类的代码并不容易,GNU Debugger (gdb) 常常指出缺少信息,无法显示相关数据,甚至可能崩溃。为了解决这个问题,可以使用 gdb 的一个高级特性 — 添加用户定义的命令。例如,请考虑一下清单 10 中的代码片段,这段代码使用一个向量并显示信息。
清单 10. 在 C++ 代码中使用 STL 向量
#include <vector>
#include <iostream>
using namespace std;
int main ()
{
vector<int> V;
V.push_back(9);
V.push_back(8);
for (int i=0; i < V.size(); i++)
cout << V[i] << "\n";
return 0;
}
现在,在调试这个程序时,如果希望查明向量的长度,可以在 gdb 提示上运行 V._M_finish – V._M_start,其中的 _M_finish 和 _M_start 分别是向量开头和末尾的指针。但是,这要求您了解STL 的内部原理,而这不总是可行的。我推荐的替代方法是使用可免费下载的 gdb_stl_utils,它在gdb 中定义几个用户定义命令,比如 p_stl_vector_size(显示向量的大小)或 p_stl_vector(显示向量的内容)。清单 11 说明 p_stl_vector 如何循环遍历由 _M_start 和 _M_finish 指针指定的数据。
清单 11. 使用 p_stl_vector 显示向量的内容
define p_stl_vector
set $vec = ($arg0)
set $vec_size = $vec->_M_finish - $vec->_M_start
if ($vec_size != 0)
set $i = 0
while ($i < $vec_size)
printf "Vector Element %d: ", $i
p *($vec->_M_start+$i)
set $i++
end
end
end
在 gdb 提示上运行 help user-defined,就可以看到使用 gdb_stl_utils 定义的命令列表。
加快编译
对于任何比较复杂的软件,编译源代码都会占用不少时间。在加快编译过程方面,最好的工具之一是ccache(见 参考资料 中的链接)。ccache 是一种编译器缓存,这意味着如果在编译期间文件没有修改过,就从工具的缓存获取它。如果用户只修改了一个头文件并调用 make clean; make,ccache会显著加快编译。因为 ccache 不仅仅使用时间戳决定文件是否需要重新编译,可以更好地节省宝贵的编译时间。下面是使用 ccache 的一个示例:
arpan@tintin# ccache g__ foo.cxx
ccache 在内部生成一个散列(hash),使用这个散列和其他东西考虑源代码文件的预处理版本(使用 g++ –E 获得)、调用编译器所用的选项等。编译的对象文件根据这个散列存储在缓存中。
ccache 定义了几个可以定制的环境变量:
* CCACHE_DIR。ccache 在这个目录中存储缓存的文件。在默认情况下,文件存储在 $HOME/.ccache中。
* CCACHE_TEMPDIR。ccache 在这个目录中存储临时文件。这个文件夹应该位于与 $CCACHE_DIR 相同的文件系统中。
* CCACHE_READONLY。如果不断增大的缓存文件夹可能造成问题,那么设置这个环境变量会有帮助。如果启用这个变量,ccache 在编译期间不会在缓存中添加任何文件;但是,它使用现有的缓存搜索对象文件。
通过结合使用 gdb 与 Valgrind 和 Electric-Fence 解决内存错误
C++ 编程有几个缺陷 — 最显著的问题是内存错误。有两个 UNIX 开放源码工具 — Valgrind 和Electric-Fence — 它们可以与 gdb 结合使用以解决内存错误。下面简要讨论如何使用这些工具。
Valgrind
对程序使用 Valgrind 最容易的方法是在 shell 上运行它,然后使用一般的程序选项。注意,为了获得最佳效果,应该运行程序的调试版本。
arpan@tintin# valgrind <valgrind options>
<program name> <program option1> <program option2> ..
Valgrind 报告一些常见的内存错误,比如不正确地释放内存(应该使用 malloc 分配,使用 delete释放)、使用尚未初始化的变量以及两次删除同一个指针。清单 12 中的示例代码中有一个明显的数组覆盖问题。
清单 12. C++ 内存错误示例
int main ()
{
int* p_arr = new int[10];
p_arr[10] = 5;
return 0;
}
Valgrind 和 gdb 可以结合使用。通过在 Valgrind 中使用 -db-attach=yes 选项,可以在运行Valgrind 时直接调用 gdb。例如,如果带 –db-attach 选项对清单 12 中的代码调用 Valgrind,在首次遇到内存问题时,它会调用 gdb,见清单 13。
清单 13. 在执行 Valgrind 期间连接 gdb
==5488== Conditional jump or move depends on uninitialised value(s)
==5488== at 0x401206C: strlen (in /lib/ld-2.3.2.so)
==5488== by 0x4004E35: _dl_init_paths (in /lib/ld-2.3.2.so)
==5488== by 0x400305A: dl_main (in /lib/ld-2.3.2.so)
==5488== by 0x400F87D: _dl_sysdep_start (in /lib/ld-2.3.2.so)
==5488== by 0x4001092: _dl_start (in /lib/ld-2.3.2.so)
==5488== by 0x4000C56: (within /lib/ld-2.3.2.so)
==5488==
==5488== ---- Attach to debugger ? --- [Return/N/n/Y/y/C/c] ---- n
==5488==
==5488== Invalid write of size 4
==5488== at 0x8048466: main (test.cc:4)
==5488== Address 0x4245050 is 0 bytes after a block of size 40 alloc'd
==5488== at 0x401ADEB: operator new[](unsigned)
(m_replacemalloc/vg_replace_malloc.c:197)
==5488== by 0x8048459: main (test.cc:3)
==5488==
==5488== ---- Attach to debugger ? --- [Return/N/n/Y/y/C/c] ----
Electric-Fence
Electric-Fence 是一组用于在基于 gdb 的环境中检测缓冲区上溢出或下溢出的库。在发生错误的内存访问时,这个工具(与 gdb 结合)会准确地指出源代码中导致问题的指令。例如,对于 清单 12中的代码,清单 14 显示在启用 Electric-Fence 的情况下 gdb 的表现。
清单 14. Electric-Fence 准确地指出源代码中导致崩溃的部分
(gdb) efence on
Enabled Electric Fence
(gdb) run
Starting program: /home/tintin/efence/a.out
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Program received signal SIGSEGV, Segmentation fault.
0x08048466 in main () at test.cc:4
<b>4 p_arr[10] = 5;</b>
在安装 Electric-Fence 之后,在 .gdbinit 文件中添加以下行:
define efence
set environment EF_PROTECT_BELOW 0
set environment LD_PRELOAD /usr/lib/libefence.so.0.0
echo Enabled Electric Fence\n
end
使用 gprof 进行代码覆盖
最常见的编程任务之一是提高代码性能。为了完成这个任务,一定要查明代码的哪些部分花费的执行时间最多。用技术术语来说,这称为剖析。GNU 剖析工具 gprof(见 参考资料 中的链接)很容易使用,提供了大量有用的特性。
为了收集程序的剖析信息,第一步是在调用编译器时指定 –pg 选项:
arpan@tintin# g++ database.cpp –pg
接下来,像一般情况下一样运行程序。成功地运行程序之后(也就是,没有出现崩溃或对 _exit system call 的调用),剖析信息被写入 gmon.out 文件中。生成 gmon.out 文件之后,对可执行文件运行 gprof(如下所示)。注意,如果没有指定可执行文件名,默认文件为 a.out。同样,如果没有指定剖析数据文件名,默认文件为当前工作目录中的 gmon.out。
arpan@tintin# gprof <options> <executable name>
<profile-data-file name> > outfile
在默认情况下,gprof 在标准输出中显示输出,所以需要把输出重定向到一个文件。gprof 提供两组信息:平面剖析数据和调用图,它们共同组成输出文件。平面剖析数据显示每个函数花费的总时间。Cumulative seconds 表示一个函数花费的总时间加上从这个函数调用的其他函数花费的时间。Self seconds 只计算这个函数本身花费的时间。
在 gdb 中显示源代码清单
开发人员常常需要通过非常缓慢的远程连接调试代码,所以不支持对 gdb 使用 Data Display Debugger (DDD) 等图形化界面。在这种情况下,在 gdb 中使用 Ctrl-X-A 组合键可以节省时间,因为它会在调试期间显示源代码清单。按 Ctrl-W-A 组合键就能够返回到 gdb 提示。另一种方法是用–tui 选项调用 gdb,这会直接启动文本模式的源代码清单。清单 15 显示以文本模式调用 gdb 的情况。
使用文本模式的 gdb 源代码清单
3using namespace std;
4
5int main ()
6 {
B+>7 vector<int> V;
8 V.push_back(9);
9 V.push_back(8);
10 for (int i=0; i < V.size(); i++)
11 cout << V[i] << "\n";
12 return 0;
13 }
14
--------------------------------------------------------------------------------------
child process 6069 In: main Line: 7 PC: 0x804890e
(gdb) b main
Breakpoint 1 at 0x804890e: file test.cc, line 7.
(gdb) r
使用 CVS 维护有秩序源代码清单
不同的项目采用不同的编码风格。例如,一些开发人员喜欢在代码中使用制表符,而其他人不喜欢这样做。但重要的是,所有开发人员应该遵守相同的编码标准。但 是,现实情况常常不是这样的。通过使用 Concurrent Versions system (CVS) 等版本控制系统,可以针对一组编码标准检查要签入的文件,从而有效地实施统一的编码标准。为了完成这个任务,CVS 附带一组预定义的触发器脚本,当涉及特定的用户操作时会运行这些脚本。触发器脚本的格式很简单:
<REGULAR EXPRESSION> <PROGRAM TO RUN>
预定义的触发器脚本之一是 $CVSROOT 文件夹中的 commitinfo 文件。为了检查要签入的文件是否包含制表符,使用以下 commitinfo 文件语法:
ALL /usr/local/bin/validate-code.pl
commitinfo 文件识别出 ALL 关键字(这意味着应该检查提交的每个文件;也可以指定要检查的文件集)。然后运行相关联的脚本,根据源代码标准检查文件。
结束语
本文讨论了几种有助于提高 C++ 开发人员的生产力的免费工具。关于各个工具的详细信息,请查阅 参考资料。
Linux c c++ 开发调试技巧的更多相关文章
- 10个Visual Studio原生开发调试技巧
10个Visual Studio原生开发调试技巧(1) 2013-05-29 13:30 佚名 开源中国 我要评论(1) 字号:T | T 以下的列表中你可以看到写原生开发的调试技巧(接着以前的文章来 ...
- iOS 开发调试技巧
对于软件开发而言,调试是必须学会的技能,重要性不言而喻.对于调试的技能,基本上是可以迁移的,也就是说你以前在其他平台上掌握的很多调试技巧,很多也是可以用在iOS开发中.不同语言.不同IDE.不同平台的 ...
- 5个Xcode开发调试技巧
转自Joywii的博客,原文:Four Tips for Debugging in XCode Like a Bro 1.Enable NSZombie Objects(开启僵尸对象) Enab ...
- iOS 5个Xcode开发调试技巧
转自Joywii的博客,原文:Four Tips for Debugging in XCode Like a Bro 1.Enable NSZombie Objects(开启僵尸对象) Enab ...
- iOS开发调试技巧总结(持续更新中)
作者:乞力马扎罗的雪 原文 对于软件开发而言,调试是必须学会的技能,重要性不言而喻.对于调试的技能,基本上是可以迁移的,也就是说你以前在其他平台上掌握的很多调试技巧,很多也是可以用在iOS开发中.不 ...
- (转)5个Xcode开发调试技巧
1.Enable NSZombie Objects(开启僵尸对象) Enable NSZombie Objects可能是整个Xcode开发环境中最有用的调试技巧.这个技巧非常非常容易追踪到重复释放的问 ...
- VSCode插件开发全攻略(六)开发调试技巧
更多文章请戳VSCode插件开发全攻略系列目录导航. 前言 在介绍完一些比较简单的内容点之后,我觉得有必要先和大家介绍一些开发中遇到的一些细节问题以及技巧,特别是后面一章节将要介绍WebView的知识 ...
- linux常用命令--开发调试篇
前言 Linux常用命令中有一些命令可以在开发或调试过程中起到很好的帮助作用,有些可以帮助了解或优化我们的程序,有些可以帮我们定位疑难问题.本文将简单介绍一下这些命令. 示例程序 我们用一个小程序,来 ...
- 《开发技巧》WEB APP开发调试技巧
前言 随着html5和nodejs的兴起.web APP越来越火,一套代码可以多平台使用.减少了很大的开发成本.很多APP中也集成了很多的html5页面,增强很高的应用体验.所以移动端页面也事关重要! ...
随机推荐
- URL中#(井号)的作用(转)
2010年9月,twitter改版. 一个显著变化,就是URL加入了"#!"符号.比如,改版前的用户主页网址为 http://twitter.com/username 改版后,就变 ...
- squee_spoon and his Cube VI(贪心,找不含一组字符串的最大长度+kmp)
1818: squee_spoon and his Cube VI Time Limit: 1 Sec Memory Limit: 128 MB Submit: 77 Solved: 22Subm ...
- The document "ViewController.xib" could not be opened. Could not read archive.
The document "ViewController.xib" could not be opened. Could not read archive. Please use ...
- Backbone入门教程
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- swiper,animate使用方法
1.先链接css和js文件 <link rel="stylesheet" type="text/css" href="css/swiper-3. ...
- Oracle包Package调用Package
Package左侧文件: create or replace package CALL_DETAILS is strdatarange varchar2(1) := '1'; numrow_num n ...
- MAVEN入门(二)
一.IDEA+MAVEN+Tomcat7 创建一个简单的Web app 1.用IDEA创建一个maven项目 注意: 红色部分一定要自己手选本地配置好的maven_home的地址,否则IDEA会选用内 ...
- C++对象的销毁
- C#中foreach语句的迭代器实现机制
C#中的foreach语句可用于循环遍历某个集合中的元素,而所有的只要支持了IEnumerable或IEnumerable<T>泛型接口的类型都是可以 用foreach遍历的.其具体的遍历 ...
- nginx启动
查看nginx的进程 ps -ef | grep nginx 重启nginx的3种办法1.service nginx restart2.改了配置文件让其生效办法 nginx -s reload3.到n ...