Linux编程环境的学习

一、vim

vim编辑器基本上可以分为3种模式,分别是命令模式、插入模式和底行模式,下图所示为Vim各种模式相互转换的关系图。

命令模式:控制屏幕光标的移动,进行文本的删除、复制等文字编辑工作(不使用[Del]键和[Backspace]键)以及进入插入模式,或者回到底行模式。

插入模式:只有在插入模式下,才可以输入文字。按[Esc]键可回到命令行模式。很多Vim编辑器使用者希望一打开Vim就可以输入内容,但这是不能成功的,因为刚打开Vim编辑器时处于命令模式。

底行模式:保存文件或退出Vim,同时也可以设置编辑环境和一些编译工作,如列出行号、寻找字符串等。

下图演示了三种模式之间的切换。

 

在命令模式下,有如下常用命令:

  1. 插入

a      //在当前光标位置的右边添加文本

i       //在当前光标位置的左边添加文本

A      //在当前行的末尾位置添加文本

I       //在当前行的开始处添加文本(非空字符的行首)

O      //在当前行的上面新建一行

  •  //在当前行的下面新建一行

 

  1. 删除

x       //删除当前字符

nx      //删除从光标开始的n个字符

dw     //删除从光标开始到一个单词(word)的末尾,实际是剪切

dd      //删除当前行,剪切

ndd     //向下删除当前行在内的n行,剪切

  1. 拷贝

yy       //将当前行复制到缓存区。

nyy      //将当前行向下n行复制到缓冲区。

yw       //复制从光标开始到词尾的字符。

nyw      //复制从光标开始的n个单词。

y^        //复制从光标到行首的内容。

y$        //复制从光标到行尾的内容。

  1. 粘帖

p(小写)        //粘贴剪切板里的内容在光标后。

P(大写)        //粘贴剪切板里的内容在光标前。

  1. 替换

r      //替换(覆盖)当前字符

R      //替换(覆盖)当前光标位置及后面的若干文本

  1. 定位

h,j,k,l       //上,下,左,右也可用键盘上的方向键。

n+         //向下跳n行

n-          //向上跳n行

nG          //跳到行号为n的行

G           //跳至文件的底部

gg          // 跳至文件的头部

ctrl-f        //下翻一页

ctrl-b       //上翻一页

^           //光标移到行首。

$           //光标移到行尾。

 

  1. 搜索文本

/vpser      //向光标下搜索vpser字符串

?vpser      //向光标上搜索vpser字符串

n           //向下搜索

N           //向上搜索

  1. 替换

:s/old/new           //用new替换行中首次出现的old

:s/old/new/g         //用new替换行中所有的old

:n,m s/old/new/g     //用new替换从n到m行里所有的old

:%s/old/new/g        //用new替换当前文件里所有的old

替换的时候注意,加’g’表示涉及的中所有匹配都替换,若不加’g’则替换行中匹配的第一个,%则相当于所有行。

  1. 撤消操作

u       //撤销上一步操作

U       //撤销对当前行的所有操作

  1. 合并

J       //合并光标所在行及下一行为一行(依然在命令模式)

当然vim还有很多常用的命令,这需要在以后的使用中再去总结。

二、makefile

对于单个源文件的项目我们可以在命令行通过g++命令一步一步的执行,比如源文件test.cpp,最终要生成可执行文件test。

我们在shell中输入如下命令

$ g++ -c test.cpp   生成test.o

$ g++ -o test test.o   生成可执行文件test

如果此时项目有两个源文件test.cpp test2.cpp 那编译就需要多加一行。会使整个过程变复杂的情况有:

(1)    源文件很多

(2)    需要很多库文件

(3)    g++命令需要很多参数

(4)    文件之间的依赖关系变的复杂

(5)    需要修改某一个源文件时

当这些情况发生时,我们再从命令行去敲就会很不方便,makefile就可以根据规则和依赖关系来简化我们的工作。在文件修改时,也只需要敲入make或gmake命令即可。总体上makefile的结构是:

 

比如生成test.o的那一步可以写成

 

同时,makefile还可以定义伪目标,比如clean就是最常用的目标,它用来清理生成的临时文件以及可执行文件。下面是一个简单完整的makefile文件

 

执行 $ make时就可以生成test文件

执行 $ make clean 就可以清除目标文件和可执行文件

当然makefile还提供很多便利的语法,比如一些宏替换和函数。

 

对于一个工程来说,该makefile很简洁,因为它所有的生成目标文件的命令只需要一行,一个SRCFILE可能相当于很多源文件。CFLAGS替换了一些编译选项,用了wildcard函数和通配符*后,$(wildcard *.cpp) 就等同于当前目录下所有源文件。同样,patsubst就是按格式替换,在第7行中是将SRCFILE宏中所有.cpp替换为.o,这正是我们需要的所有目标文件。12、13行会将目录下的cpp文件挨个编译。当我们需要修改时,只需要修改很少的一部分,比如要给编译选项加入Werror,只需要修改第三行即可。当然一个好的makefile文件还需要恰当的注释,makefile采用’#’符号进行行注释。

三、g++

g++是GNU C++编译器。它在执行编译工作需要4步:

  1. 预处理,生成.i的文件[预处理器cpp]
  2. 将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs]
  3. 由汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
  4. 连接目标代码,生成可执行程序[链接器ld]

在我们的平时使用过程中,前三步都是一步完成。比如我们执行

       $ g++ -c test.cpp

就可以直接生成了test.o。g++在使用过程中经常需要根据自己的需求加入一些选项,常用的选项和解释如下:

-g/-ggdb  -g是在编译的时候生成调试信息,只有加入此选项生成可执行文件才能用gdb调试,-ggdb选项是让编译过程尽可能多的生成调试信息。

-O0/-O1/-O2/-O3 这是编译器的4个优化级别,从左往右优化级别变高。

-Wall 是warn all的缩写,它会显示所有g++可以告诉我们的警告信息。

-Werror  将所有的警告变成错误,即该条命令执行失败,和-Wall结合使用。

-Wunused-parameter 当函数定义的形参在函数中没有用到时,就会产生警告信息。

-Wformat 该选项用于检查在调用printf和scanf等函数时,格式串是否正确。若不正确则产生警告信息。该选项已经包含在-Wall中了。 例printf("abc\n", i);

-Wconversion 一些类型转化让一个值发生变化时就产生警告信息,比如浮点数和定点数之间的转化,还有负整数到非负类型的隐式转化(如unsigned int x = -1)。在实际中,感觉加不加该选项都会有同样的警告。

-Wdeprecated  使用已过时或者弃用的文件产生警告,如在c++程序中采用头文件iostream.h

-finline-functions  该选项将所有将所有简单函数的代码直接嵌入到调用者的代码中,如果对一个给定函数的所有调用都是采用嵌入代码的方式,并且该函数声明为静态函数,那么该函数将不会输出为汇编代码。该选项只有在-O3优化选项下生效。

四、gdb

一般来说,GDB主要帮忙你完成下面四个方面的功能:

  1. 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
  2. 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时你的程序中所发生的事。
  4. 动态的改变你程序的执行环境。

常用的一些功能及相应命令如下:

启动gdb: $ gdb  $file test 或者直接 $ gdb test

退出gdb: $ quit即可

运行程序: $ run

查看信息: $ info

       查看断点信息: $ info break

       查看观察点信息: $ info watchpoint

       查看当前源程序: $ info source

       查看堆栈信息: $ info stack

       查看当前参数: $ info args

列出一段源程序: $ list

       列出某个函数: $ list

       以当前源文件的某行为中间显示一段源程序: $ list LINENUM

       接着前一次继续显示: $ list

       显示前一次之前的源程序: $ list -

       显示另一个文件的一段程序: $ list FILENAME  或$ list FILENAME:LINENUM

断点操作: $ break

       在函数入口设置断点: $ break

       在当前源文件的某一行设置断点: $ break LINENUM

在另一个源文件的某一行设置断点: $ break FILENAME:LINENUM

启用断点: $ enable 2

禁止断点: $ disable 2

清除断点: $ delete 2

观察点操作

$ watch EXPR

我们知道断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断,如果我们不知道某个存储单元是在哪里被改动的,这时候观察点尤其有用。

$ rwatch EXPR:

       使用EXPR作为表达式设置一个断点,当EXPR被程序读取时,程序被gdb暂停;

$ awatch EXPR:

       使用EXPR作为表达式设置一个观察点,当EXPR被读出然后被写入时,gdb会暂停程序;这个命令常

进入下一条语句 next  step可以进入函数

退出函数, finish

线程操作

A、thread THREAD_NO: 该命令用于在线程之间进行切换,把线程号为THREAD_NO(gdb设置的线程号)的线程设置为当前线程;

    B、info threads: 查询当前进程所拥有的所有线程的状态摘要信息;gdb按照顺序显示:

       a、线程号: gdb为被调试进程中的线程设置的顺序号;

       b、目标系统的线程标识;

       3、此线程的当前栈信息;

       一些前面带'*'号的线程,表示该线程是当前线程;

    C、thread apply [THREAD_NO] [ALL] ARGS: 该命令用于向线程提供命令;

查看变量, print 检查内存值的指令是x,x是examine的意思。用法如下:

x /NFU ADDR

其中N代表重复数,F代表输出格式,U代表每个数据单位的大小。U可以取如下值:

b :字节(byte)

h :双字节数值

w :四字节数值

g :八字节数值

  因此,上面的指令可以这样解释:从ADDR地址开始,以F格式显示N个U数值。例如:

   x/4ub 0x4000

会以无符号十进制整数格式(u)显示四个字节(b),0x4000,0x4001,0x4002,0x4003。缺省情况下,输出格式依赖于它的数据类型。但你可以改变输出格式。当你使用print命令时,可以用一个参数/F来选择输出的打印格式。F可以是以下的一些值:

  'x' 16进制整数格式

  'd' 有符号十进制整数格式

  'u' 无符号十进制整数格式

  'f' 浮点数格式

再比如char input[5];

(gdb) x/7b input

x命令打印指定存储单元的内容。7b是打印格式,b表示每个字节一组,7表示打印7组,从input数组的第一个字节开始连续打印7个字节。

切换栈

bt 查看堆栈信息

up n 向上n层

down n  向下n层

frame  会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。

用gdb调试core文件

core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump。 (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)

在程序运行的过程中,有的时候我们会遇到Segment fault(段错误)这样的错误。这种看起来比较困难,因为没有任何的栈、trace信息输出。该种类型的错误往往与指针操作相关。往往可以通过这样的方式进行定位。

造成segment fault,产生core dump的可能原因:

1、内存访问越界

              a) 由于使用错误的下标,导致数组访问越界

b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符

c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

2、 多线程程序使用了线程不安全的函数。

3、 多线程读写的数据未加锁保护。

对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

4、 非法指针

a) 使用空指针

b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它 时就很容易因为bus error而core dump.

5、 堆栈溢出

不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

 

配置操作系统使其产生core文件

首先通过ulimit命令查看一下系统是否配置支持了dump core的功能。通过ulimit -c或ulimit -a,可以查看core file大小的配置情况,如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。若发生了段错误,但没有core dump,是由于系统禁止core文件的生成。

解决方法:

$ulimit -c unlimited  (只对当前shell进程有效)

或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)

# ulimit -c

0

$ ulimit -a 

core file size          (blocks, -c) 0

data seg size           (kbytes, -d) unlimited

file size               (blocks, -f) unlimited

用gdb查看core文件

发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行.

$ gdb [exec file] [core file]

如:  $ gdb ./test test.core

然后使用 bt查看调用堆栈。

 

五、twiki

这部分感觉在wiki尝试几次就可以熟悉了。

posted on 2012-07-11 20:13  吃吃户  阅读(735)  评论(0编辑  收藏  举报