Linux自学笔记(一)

一、内存的分类

         一个程序中使用到的数据都是存放在内存空间中,那么执行一个程序,为了考虑其高效性和灵活性等都要合理的分配内存,根据内存空间分配方式的不同,可以将内存分为静态内存和动态内存。下面分别对动态内存和静态内存进行讲解。

1.1  动态内存

通常当用户无法确定空间大小、或者空间太大,栈上无法分配时,会采用动态内存方式分配内存。在使用动态内存时,程序员可以自行控制内存的分配和释放,关于分配多少,何时分配与释放等信息,都由程序员根据需要随时实现。

关于动态内存的使用,很多程序员采取能不使用就不使用的原则,原因在于动态内存资源的敏感性,若能够经验老道的正确使用并且利用好动态内存,自然会为程序的实现带来效率。但是一旦用不好,就有可能导致整个项目都坍塌。因此关于动态内存的使用,不同程度的人应遵循不同的使用原则,目的只为了能够使程序安全、正确并且快速的实现其功能。

事物都是有利便有弊,动态内存也不除外,当动态内存为程序带来了巨大的效率的同时,也为程序带来了巨大的风险。使用动态内存会使内存管理变得很复杂。当程序员根据自己的需要动态的分配完内存,就可以得心应手的使用,但是当不再使用时,一定要切记,释放所占的内存空间。所谓的内存泄露就是将内存分配后没有释放而导致的内存空间减少的现象。计算机的内存空间是有限的,当分配了过多的内存而没有及时释放,很有可能导致内存不够用,也就是通常所说的内存耗尽。内存分配与释放是配对的,分配的内存在哪里,使用完毕就要释放哪里的内存。在一个大型的项目中,如若多次分配内存,那么释放这些内存的顺序就成为了难题。

 

1.2  静态内存

所谓静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。程序中的各种变量,在编译源程序时系统就已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会自动释放所占用的内存空间。变量的分配与释放,都无须程序员自行考虑,因此使用静态内存对程序员来说很方便。不必像动态内存那样,要掌握分配内存的大小、何时分配与释放等细节。

使用静态内存减少了很多内存资源的风险,如内存泄露、内存耗尽等问题,减少了风险的同时也给静态内存带来了弊端。在使用一个数组时,静态内存会预先定义数组的大小,定义数组前并不确定数组中会存放多少数据,若在使用时,存放在数组中的数据大于数组的容量,那么,就会出现溢出问题;然而存放在数组中的数据小于数组的容量很多时,就会造成内存空间的浪费。

静态内存的分配是由编译器来分配的,释放是由变量的作用域所决定,即当一个变量定义在一个自定义的功能函数中时,那么当这个函数结束时,该变量也随之释放。那么使用指针由子函数向主函数传递数据类的问题就无法实现了,因为子函数中的变量在子函数结束时,就会被释放,因此无法将值带回到主函数。但是事情总会有解决的办法,那就是可以在主函数中定义变量,在子函数中使用主函数中定义的变量传递值。

 

1.3  动态内存与静态内存的区别

动态内存与静态内存是两种不同的分配内存的方式,下面概括一下,它们在分配方式上存在什么样的区别?

(1)静态内存的分配是在程序开始编译时完成的,不占用CPU资源;而动态内存的分配是在程序运行时完成的,动态内存的分配与释放都是占用CPU内存的。

(2)静态内存是在栈上分配的;而动态内存是在堆上分配的。

(3)动态内存分配需要指针和引用数据类型的支持,而静态内存则不需要。

(4)静态内存分配时在编译前就已经确定了内存块的大小,属于按计划分配内存;而动态内存的分配是在程序运行过程中,根据需要随时分配的,属于按需分配。

(5)静态内存的控制权是交给编译器的,而动态内存的控制权是由程序员决定的。

 

二、分配内存

2.1  分配内存

计算机的内存空间,都是通过指针进行访问的,而对于指针,能够正确的分配动态内存空间又是十分重要的。关于动态内存的分配所使用的操作函数在这里主要介绍malloc函数、calloc函数、realloc函数和memset函数的基本用法。

(1)malloc函数

函数原型为:

void *malloc(unsignedint size);

该函数的功能是分配长度为size字节的内存块。

如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。注意当内存不再使用时,要使用free()函数释放内存块。例如使用malloc函数获得一块内存空间,内存空间的大小与返回的指针类型由程序员根据需要自行规定,代码如下:

void main()

{

         long* buffer;

         buffer = (long *)malloc(400);   //获得一块长整型数组空间

         free(buffer);                                          //释放内存空间

}

 

(2)calloc函数

函数原型为:

void *calloc(unsigned n,unsigned size);

该函数的功能是在内存的动态区存储中分配n个长度为size的内存块。

如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL,同样在内存不再使用时要用free()函数释放内存块。

同时,用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。

例如,使用calloc函数获得一块长整型数组空间,代码如下:

void main()

{

         long* buffer;

         buffer = (long*)calloc(20,sizeof(long));   //获得一块长整型数组空间

         free(buffer);                                                                      //释放内存空间

}

 

(3)realloc函数

函数原型为:

void *realloc(void *mem_address,unsigned intnewsize);

该函数的功能是调整mem_address所指内存区域的大小为newsize长度。

如果重新分配内存成功,则返回指向被分配内存的指针,否则返回空指针NULL。并且当内存不再使用时,要应用free()函数将内存空间释放。

当参数mem_address指向NULL时,即调整空指针所指向的内存区域的大小为newsize长度,此时realloc函数的功能就如同malloc函数。若参数newsize为0,即要调整成的长度为0时,函数realloc所实现的功能相当于free函数,释放掉该内存区块。

 

例1、在vim编辑器中编写一个简单的C语言程序,使用realloc函数重新分配一块内存空间。

#include

#include

main()

{

         char *p;

         p=(char *)malloc(100);                                          /* 为指针p开辟一个内存空间*/

         if(p)                                                                             /*判断内存分配成功与否*/

                   printf("Memory Allocatedat: %x",p);

    else

        printf("Not EnoughMemory!\n");

    getchar();

         p=(char *)realloc(p,256); /*调整p内存空间从100字节调整到256字节*/

    if(p)

                   printf("MemoryReallocated at: %x",p);

    else

        printf("Not EnoughMemory!\n");

         free(p);                                                                 /*释放看p所指向的内存空间*/

    getchar();

    return 0;

}

 

(4)memset函数

函数原型为:

void *memset(void *s,char ch,unsigned n);

该函数的功能是设置s中的所有字节为ch,s数组的大小为n。

例2、在vim编辑器中编写一个简单的C语言程序,演示memset函数的功能,使用字符‘*’替换数组s中的字符串。

#include

#include

int main(void)

{

char s[] ="welcome to mrsoft\n";                   /*定义一个字符数组s*/

printf("sbefore memset: %s\n", s);                /*输出字符数组中的内容*/

memset(s, '*',strlen(s) - 1);                     /*设置s数组中的字符串内容为“*”*/

printf("safter memset: %s\n", s);                   /*输出此时的字符数组内容*/

return 0;

}

 

2.2、释放内存

在通过malloc函数、calloc函数和realloc函数分配完动态内存后,在程序中可以使用这些内存空间,然而使用完动态内存时,一定要使用free函数手动释放掉该块内存空间,以免造成内存泄露等问题。当释放掉内存后,原来指向内存空间的指针就会变成悬空的指针,这时再使用该指针时会产生错误。

free函数的原型为:

void free( void *memblock );

参数memblock表示要被释放的内存区块。

 

三、链表

使用链表或者队列等数据结构时通常会使用动态内存存储数据。链表是一种动态地进行存储分配的一种结构,是根据需要开辟内存单元。

创建动态链表就是指在程序执行过程中,从无到有,按照需求开辟结点和输入各结点数据,并建立起前后相链的关系。通常链表中的结点会使用结构体变量这个数据类型的变量,原因在于,这样一个结点就可以表示多个不同数据类型的相关联的信息了。在动态链表中,必须利用指针变量才能实现结点与结点之间相连接,因此在一个结点中应包含一个指针变量,用它存放下一个结点的地址。例如,可以设计这样一个结构体类型:

struct student

{

         int num;

         int age;

         float score;

         struct student *next;/*指向链表的下一个结点*/

};

例1、在vim编辑器中编写一个简单的C语言程序,实现创建一个学生链表,学会如何动态的分配所需的内存空间,以及如何通过链表,将存储在内存空间中的数据输出到控制台。

(1)创建学生链表的全部代码如下:

#include

#include

#define LENsizeof(struct student)

typedef structstudent

{

         int num;

         int age;

         float score;

         struct student *next;                /*指向链表的下一个结点*/

}stu;                                                         /*声明结构体类型structstudent,并取别名为stu*/

int n;

stu *creat(void)                                    /*创建动态链表函数*/

{

         stu *head,*p1,*p2;                   /*定义结构体类型的指针*/

         n=0;

         p1=p2=(stu *)malloc(LEN);      /*开辟一个内存空间*/

         scanf("%d,%d,%f",&p1->num,&p1->age,&p1->score); /*输入结构体类型的数据*/

         head=NULL;                                 /*头指针置空*/

         while(p1->num!=0)                    /*判断学号输入是否为0,若是0则跳出循环*/

         {

                   n=n+1;                       

                   if(n==1)head=p1;              

/*判断是否是输入的第1个数据信息,若是第一个数据信息,则将头指针指向p1*/

                   else

                            p2->next=p1;           /*将p2指向的下一个地址指向p1*/

                   p2=p1;                                  /*p2指向p1*/

                   p1=(stu *)malloc(LEN);   /*再次为p1开辟一个内存空间,存储下一个数据*/

         scanf("%d,%d,%f",&p1->num,&p1->age,&p1->score);

         }

         p2->next=NULL;                          /*p2指向下一个地址指向的是空指针*/

         return(head);                               /*返回数据信息的头指针,以便从头输出*/

}

main()

{

         stu *p,*head;

         head=creat();

         p=head;                                         /*p指向头指针*/

         if(head!=NULL)                            /*判断头指针是否为空,不为空则执行循环体输出信息*/

                   do

                   {

                            printf("%d,%d,%f\n",p->num,p->age,p->score);

                            p=p->next;

                   }while(p!=NULL);

}

 

四、VIM命令行操作

在进入vim之后,也就进入了vim的命令行模式,在命令行模式下可以进行如下操作:

进入插入模式

i                光标前插入在光标左侧输入正文

I                 在光标所在行的开头输入正文

a                光标后插入在光标右侧输入正文

A               在光标所在行的末尾输入正文

o               在光标所在行的下一行增添新行

O              在光标所在行的上一行增添新行

 

移动光标

hjkl       左、下、上、右

Ctrl+b    在文件中向上移动一页(相当于 PageUp 键)

Ctrl+f   在文件中向下移动一页(相当于 PageDown 键)

G          移到文件最后

H          将光标移到屏幕的最上行(Highest)

nH           将光标移到屏幕的第 n 行

M         将光标移到屏幕的中间(Middle)

L          将光标移到屏幕的最下行(Lowest)

nL          将光标移到屏幕的倒数第 n行

w          在指定行内右移光标,到下一个字的开头

e          在指定行内右移光标,到一个字的末尾

b          在指定行内左移光标,到前一个字的开头

0          数字0,左移光标,到本行的开头

$          右移光标,到本行的末尾

^           移动光标,到本行的第一个非空字符

 

删除

x          删除光标所指向的当前字符

nx        删除光标所指向的前 n 个字符

:1,#d     删除行1至行#的文字

X          删除所在前面一个字符

D         删除至行尾

dw         删除光标右侧的字

ndw        删除光标右侧的 n 个字

db         删除光标左侧的字

ndb        删除光标左侧的 n 个字

dd         删除光标所在行

ndd         删除 n 行内容

 

更改

cw        更改光标处之字到此一单词之字尾处

c#w       例,c3w表更改 3 个单词

cc        修改行

 

取代

r       取代光标处之字符

R         取代字符直到按 ESC 为止

 

复制和粘贴

yw        拷贝光标处之字到字尾至缓冲区

yy         拷贝光标所在之行至缓冲区

#yy        5yy,拷贝光标所在之处以下5行至缓冲区

P        把缓冲区之资料贴在所在行的后 

p        把缓冲区之资料贴在所在行的前

 

撤销

u          undo,复原至上一动作

 

重复上一个命令

.                 重复上一个命令

 

五、VIM的编辑模式

上面在命令行模式中讲到了如何从命令行进入编辑模式的操作,而且要进入vim的编辑模式就必须通过命令行进入。在进入了vim的编辑模式后,用户就可以对打开的文件进行编辑操作,尤其现在的vim已经支持鼠标操作,使用起来就更加方便。

 

六、VIM的底行操作模式

vim的底行模式也叫末行模式,就是在界面最底部进行命令的输入,一般是用来保存和退出等任务。只要在命令行模式下输入冒号,就可以进入底行模式。比起命令行的那么多命令,底行模式的命令就明显少得多了。

Vim底行模式的基本操作如下所示:

退出命令

:wq或:x                               先保存再退出vi

:w或 :w filename   保存/保存为filename名的文件

:q                                           退出(如果文件被修改会有提示)

:q!或:quit                            不保存退出vi

:wq!                             强制保存,并退出

 

显示和取消行号

:set nu        显示行号

:set nonu    不显示行号

字符串搜索

:/str              正向搜索,将光标移到下一个包含字符串 str 的行,按 n 可往下继续找

:?str               反向搜索,将光标移到上一个包含字符串 str 的行按n可往前继续找

:/str/ w file    正向搜索,并将第一个包含字符串 str 的行写入 file 文件

:/str1/,/str2/w  file  正向搜索,并将包含字符串 str1 的行至包含字符串 str2 的行写入file 文件

 

删除正文

:d                                           删除光标所在行

:3  d                                    删除 3 行

:.,$  d                 删除当前行至正文的末尾

:/str1/,/str2/ d        删除从字符串 str1 到 str2 的所有行

 

恢复文件

:recover                      恢复文件

 

七、初识Emacs

Emacs是一种强大的文本编辑器,在程序员和其他以技术为主的计算机用户中广受欢 迎。Emacs,即EditorMacros   (编辑器宏)的缩写,最初由RichardStallman(理查德·马修·斯 托曼)于 1975年在MIT协同Guy Steele共同完成。这一创意的灵感来源于TECMAC和TMACS,它们是由Guy Steele、Dave Moon、Richard Greenblatt、Charles Frankston等人编写的宏文本编辑器。自诞生以来,Emacs演化出了众多分支,其中使用最广泛的两种分别是:1984 年由 Richard Stallman发起并由他维护至今的GNU Emacs,以及 1991 年发起的XEmacs 。XEmacs 是GNU Emacs的分支,至今仍保持着相当的兼容性。它们都使用了Emacs Lisp这种有着极强扩展性的编程语言,从而实现了包括编程、编译乃至网络浏览等等功能的扩展。        

 

7.1、启动Emacs

Emacs 的基本操作可以参考Emacs 自带的Tutorial,有中文版的,非常全面,在学习的 时候可以很方便的进行查阅。

启动Emacs 只需在命令行键入“emacs   [文件名]”(若缺省文件名,也可在emacs编辑文件后另存时指定),也可从“编程”→“emacs”打开,接着可单击任意键进入Emacs 的工作窗口。Emacs 的工作窗口分为上下两个部分,上部为编辑窗口,底部为命令显示窗口,用户执行功能键的功能都会在底部有相应的显示,有时也需要用户在底部窗口输入相应的命令,如查找字符串等。

 

7.2、基本操作

在进入 Emacs 后,即可进行文件的编辑。由于 Emacs 只有一种编辑模式,因此用户无需进  行模式间的切换。下面介绍Emacs中基本编辑功能键。   下文操作中的 C 表示CTL 键,M 表示ALT 键。 

1)移动光标

虽然在 Emacs中可以使用“上”、“下”、“左”、“右”方向键来移动单个字符,但笔者还是建议读者学习其对应功能键,因为它们不仅能在所有类型的终端上工作,而且读者将会发现在熟练使用之后,输入这些 Ctrl 加字符会比按方向键快很多。

Emacs 光标移动功能键

C-f         向前移动一个字符

M-b       向后移动一个单词

C-b            向后移动一个字符

C-a       移动到行首

C-p           移动到上一行

C-e       移动到行尾

M-<(M 加 “小于号”)     移动光标到整个文本的开头

M->(M 加大于号”)  移动光标到整个文本的末尾

C-n          移动到下一行      

M-f 向前移动一个单词

2)剪切和粘贴

在 Emacs  中可以使用“Delete”和“BackSpace”删除光标前后的字符,这和用户之前的习惯一致,在此就不赘述。以词和行为单位的剪切和粘贴功能键如下:

Emacs 剪切和粘贴

M-Delete         剪切光标前面的单词

M-k               剪切从光标位置到句尾的内容

C-y                      将缓冲区中的内容粘贴到光标所在的位置

M-d                    剪切光标前面的单词

C-k                   剪切从光标位置到行尾的内容 

C-x  u              撤销操作(先操作 C-x,接着再单击u )

注意:在Emacs中对单个字符的操作是 “删除”,而对词和句的操作是 “剪切”,即保存在缓冲区中,以备后面的 “粘贴”所用。

3)复制文本

在Emacs 中的复制文本包括两步:选择复制区域和粘贴文本。

选择复制区域的方法是:首先在复制起始点按下“C-Spase”或“C-@(C-Shift-2)”使它成为一个表示点,再将光标移至复制结束电,再按下“M-w”,就可将A 与B 之间的文本复制到系统的缓冲区中。在使用功能键C-y 将其粘贴到指定位置。

4)查找文本

查找文本的功能键如下所示:

Emacs 查找文本功能键

C-s 查找光标以后的内容,并在对话框的“I-search:”后输入查找字符串

C-r  查找光标以前的内容,并在对话框的“I-search   backward:”后输入查找字符串

5)保存文档

在 Emacs  中保存文档的功能键为“C-xC-s”(即先操作C-x,接着再操作C-s)。另外,Emacs 在编辑时会为每个文件提供“自动保存(auto save)”的机制,而且自动保存的文件的文件名前后都有一个“#”,例如,编辑名为“hello.c ”的文件,其自动保存的文件的文件名就叫“#hello.c#”。当用户正常的保存了文件后,Emacs 就会删除这个自动保存的文件。这个机制当系统发生异常时非常有用。

6)退出文档

在Emacs 中退出文档的功能键为“C-x C-c”。

 

八、gcc编译器

在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器。目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、 Fortran、Pascal、Modula-3和Ada等。

Linux系统下的Gcc(GNU CCompiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。

 

8.1、第一次编译

在学习使用GCC之前,下面的这个例子能够帮助用户迅速理解GCC的工作原理,并将其立即运用到实际的项目开发中去。首先用熟悉的编辑器输入如下所示的代码:

#include

int main()

{

     printf("hello word!Linux c!\n");

     return 0;

}

将上面的代码保存为hello.c,然后用户就可以在终端中对上面的C语言代码进行编译了。并且我们给编译出的新文件其名为hello,最后执行编译好的文件。

上面在编译的时候,为gcc的后面加入了选项-o进行新文件的重命名,如果不加入这个选项,那么新文件就会默认为a.out,如果再次编译其他的文件,同样不进行重命名的话,那么这里的a.out将会被覆盖掉。

 

8.2、选项概述

在使用Gcc编译器的时候,我们必须给出一系列必要的调用参数和文件名称。Gcc编译器的调用参数大约有100多个,其中多数参数我们可能根本就用不到,这里只介绍其中最基本、最常用的参数。

Gcc最基本的用法是∶

gcc [options] [filenames]

其中options就是编译器所需要的参数,filenames给出相关的文件名称。

-c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。

-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。

-g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。

-O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。

-O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。

 

8.3、警告

GCC包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员写出更加专业和优美的代码。先来看看下面所示的程序,这段代码写得很有问题,仔细检查一下不难挑出很多毛病:

#include

void main(void)

{

long long int var= 1;

printf("It isnot standard C code!\n");

}

main函数的返回值被声明为void,但实际上应该是int;

使用了GNU语法扩展,即使用long long来声明64位整数,不符合ANSI/ISO C语言标准;

main函数在终止前没有调用return语句。

下面来看看GCC是如何来发现这些错误的。当GCC在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:

# gcc -pedantic illcode.c -o illcodeillcode.c: In function `main': illcode.c:9: ISO C89 does not support `longlong' illcode.c:8: return type of `main' is not `int'

值得注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合 ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息:

# gcc -Wall illcode.c -o illcodeillcode.c:8: warning: return type of `main' is not `int' illcode.c: In function`main': illcode.c:9: warning: unused variable `var'

GCC给出的警告信息虽然从严格意义上说不能算作是错误,但却很可能成为错误的栖身之所。但是作为一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的特性。

在处理警告方面,另一个常用的编译选项是-Werror,它要求GCC将所有的警告当成错误进行处理,这在使用自动编译工具(如Make等)时非常有用。如果编译时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消除时,才可能将编译过程继续朝前推进。执行情况如下:

# gcc -Wall -Werror illcode.c -o illcodecc1: warnings being treated as errors illcode.c:8: warning: return type of`main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unusedvariable `var'

对Linux程序员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。

 

8.4、gcc调试

一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到解决问题的方法。对于Linux程序员来讲,GDB(GNU Debugger)通过与GCC的配合使用,为基于Linux的软件开发提供了一个完善的调试环境。

默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用GCC的-g或者-ggdb选项。GCC在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。

GCC产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是GDB,那么还可以通过-ggdb选项在生成的二进制代码中包含 GDB专用的调试信息。这种做法的优点是可以方便GDB的调试工作,但缺点是可能导致其它调试器(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,它们对输出的调试符号有着相同的影响。

值得注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码大小的影响从下面的对比过程中可以看出来:

 

# gcc optimize.c -o optimize # ls optimize-l -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加调试选项) # gcc-g optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov20 08:54 optimize (加入调试选项)

 

虽然调试选项会增加文件的大小,但事实上Linux中的许多软件在测试版本甚至最终发行版本中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,是Linux的一个显著特色。

下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序如下所示(代码名称为crash.c)。

#include

int main(void)

{

int input =0;

printf("Inputan integer:");

scanf("%d",input);

printf("Theinteger you input is %d\n", input);

return 0;

}

编译并运行上述代码,会产生一个严重的段错误(Segmentation fault)如下:

# gcc -g crash.c-o crash # ./crash Input an integer:10 Segmentation fault

为了更快速地发现错误所在,可以使用GDB进行跟踪调试,方法如下:

# gdb crash GNUgdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)

当GDB提示符出现的时候,表明GDB已经做好准备进行调试了,现在可以通过run命令让程序开始在GDB的监控下运行:

 (gdb) run Starting program:/home/xiaowp/thesis/gcc/code/crash Input an integer:10 Program received signalSIGSEGV, Segmentation fault. 0x4008576b in _IO_vfscanf_internal () from/lib/libc.so.6

仔细分析一下GDB给出的输出结果不难看出,程序是由于段错误而导致异常中止的,说明内存操作出了问题,具体发生问题的地方是在调用 _IO_vfscanf_internal ( )的时候。为了得到更加有价值的信息,可以使用GDB提供的回溯跟踪命令backtrace,执行结果如下:

 (gdb) backtrace #0 0x4008576b in_IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #20x4008e0ba in scanf () from /lib/libc.so.6 #3 0x08048393 in main () atcrash.c:11 #4 0x40042917 in __libc_start_main () from /lib/libc.so.6

跳过输出结果中的前面三行,从输出结果的第四行中不难看出,GDB已经将错误定位到crash.c中的第11行了。现在仔细检查一下:

 (gdb) frame 3 #3 0x08048393 in main () at crash.c:1111 scanf("%d", input);

使用GDB提供的frame命令可以定位到发生错误的代码段,该命令后面跟着的数值可以在backtrace命令输出结果中的行首找到。现在已经发现错误所在了,应该将

scanf("%d",input); 改为 scanf("%d", &input);

完成后就可以退出GDB了,命令如下:

(gdb) quit

 

GDB的功能远远不止如此,它还可以单步跟踪程序、检查内存变量和设置断点等。

调试时可能会需要用到编译器产生的中间结果,这时可以使用-save-temps选项,让GCC将预处理代码、汇编代码和目标代码都作为文件保存起来。如果想检查生成的代码是否能够通过手工调整的办法来提高执行性能,在编译过程中生成的中间文件将会很有帮助,具体情况如下:

# gcc -save-tempsfoo.c -o foo # ls foo* foo foo.c foo.i foo.s

 

GCC 支持的其它调试选项还包括-p和-pg,它们会将剖析(Profiling)信息加入到最终生成的二进制代码中。剖析信息对于找出程序的性能瓶颈很有帮助,是协助Linux程序员开发出高性能程序的有力工具。在编译时加入-p选项会在生成的代码中加入通用剖析工具(Prof)能够识别的统计信息,而- pg选项则生成只有GNU剖析工具(Gprof)才能识别的统计信息。

最后提醒一点,虽然GCC允许在优化的同时加入调试符号信息,但优化后的代码对于调试本身而言将是一个很大的挑战。代码在经过优化之后,在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

 

8.5、代码优化

代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。GCC提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的GCC来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。

下面通过具体实例来感受一下GCC的代码优化功能,所用程序如下面所示(代码名称为optimize.c)。

 

#include

int main(void)

{

double counter;

double result;

double temp;

 

for (counter = 0;counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4)

{

temp = counter /1979; result = counter;

}

printf("Resultis %lf\n", result); return 0;

}

 

首先不加任何优化选项进行编译:

 

# gcc -Walloptimize.c -o optimize

 

借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:

 

# time ./optimizeResult is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s

 

接下去使用优化选项来对代码进行优化处理:

 

# gcc -Wall -Ooptimize.c -o optimize

 

在同样的条件下再次测试一下运行时间:

 

# time ./optimizeResult is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s

 

对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。

优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码:

1)程序开发的时候 优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。

2)资源受限的时候 一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。

3)跟踪调试的时候 在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。

 

九、gcc编译基本流程

9.1、C预处理

C预处理器CPP是用来完成对于程序中的宏定义等相关内容进行先期的处理。一般是指那些前面含有“#”号的语句,这些语句一般会在CPP中处理。例如:

#define MR(25*4)

Printf(“%d”,MR*5);

经过CPP的处理后,就会变成如下格式传递到代码中:

printf(“%d”,(25*4)*5)

其实不难看出,CPP的作用就是解释后定义和处理包含文件。在gcc中使用时,gcc会自动调用CPP预处理器。

 

9.2、编译

编译的过程就是将输入的源代码和预处理相关文件编译为“.o”的目标文件。

 

9.3、汇编

在使用gcc编译程序的时候,会产生一些汇编代码,而处理这些汇编代码就需要使用汇编器as,as可以处理这些汇编代码,从而使其成为目标文件,最终目标文件转换成.o文件或其他可执行文件。而且as汇编器和CPP一样,可以被gcc自动调用。

 

9.4、链接

在处理一个较大的C语言项目时,我们通常会将程序分割成很多模块,那么这时候就需要使用连接器将这些模块组合起来,并且结合相应的C语言函数库和初始代码,产生最后的可执行文件。连接器一般在一些大的程序和项目中,对最后生成可执行文件起着重要的作用。

虽然gcc可以自动调用连接器,但是为了更好的控制连接过程,建议最好手动调用连接器。

 

十、gdb调试器

无论是刚刚接触编程的初学者还是已经在编程工作上有着丰富经验的工程师,在编写一个程序时,往往会出现意想不到的错误,实现同一功能的程序算法可能是一样的,但是出现错误的原因却可能是千奇百怪的。因此在完成一个项目后,必不可缺的是对这个项目程序的调试与多次测试。gdb调试器就是在Linux平台上最常用的调试工具。通过设置断点、单步跟踪、显示数据等功能可以快速查找到故障点,对程序进行改正完善。

 

10.1、gdb调试器概述

在Linux平台下,GNU发布了一款功能强大的调试工具,称为gdb(GNU Debugger),该软件最早是由Richard Stallman编写的,gdb是一个用来调试C和C++程序的调试工具。其工作在命令模式下,需要通过输入命令来实现对应用程序的调试。通过此调试工具可以在程序运行时观察程序的内部结构和内存的使用情况。

关于gdb调试器,它是在终端,通过输入命令,进入调试界面的,在调试的过程中,也是通过命令来进行调试的。在终端中输入gdb命令,就可以进入到gdb调试的界面,

gdb调试器主要实现三方面的功能,分别如下:

(1)启动被调试的程序。

(2)使被调试的程序在指定位置停住。

(3)当程序被停住时,可以检查程序此时的状态,如变量的值。

为了使调试器实现上述三方面功能,可以使用如下五条命令进行操作。

(1)启动程序:启动程序时,可以设置程序的运行环境,使程序运行在gdb调试环境下。

(2)设置断点:在运行程序时,程序会在断点处停住,方便用户查看程序此时的运行情况,断点可以是行数,可以是函数名称或者条件表达式。

(3)查看信息:可以查看和可执行程序相关的各种信息。

(4)分布运行:可以使代码一句一句的执行,方便及时查看程序的信息。

(5)改变环境:可以在程序运行时改变程序的运行环境和程序变量。

 

10.2、启动调试程序功能及其命令

通过前一节介绍的简单的调试过程,已经了解了gdb调试工具的主要功能和几个简单的常用命令。gdb调试工具之所以可以成为Linux平台上应用最广泛的调试工具,是因为其具有强大的调试功能,接下来了解一下gdb调试工具的基本功能和相应的命令。以如下代码实例作为调试的一个程序,从应用中了解实现这些功能的命令是如何操作的。

1)启动调试程序功能及其命令

使用gdb调试程序,必须要让gdb可以获得程序的信息,因此需要在编译程序的时候加入参数g,编译命令如:

gcc –g –o 可执行文件名 源程序文件名

生成一个带有调试信息的可执行文件,由此,可以使用如下命令语句加载可执行文件程序进入到gdb调试工具中。

gdb 可执行文件名

进入gdb调试工具的另一种方法是,可以先输入gdb命令(在命令行中输入gdb,回车),然后通过文件命令操作加载可执行文件。如

file 可执行文件名

进入gdb调试工具后,可以使用gdb命令run运行程序,在命令行中输入run,回车就可以运行程序。当调试结束,可以输入命令quit,回车退出gdb调试工具,也可以使用ctrl+d快捷键退出gdb调试工具。上述启动程序命令采用了在gdb命令中加载可执行文件的方式进入gdb调试工具中,实现过程

 

10.3、检查数据功能及其命令

在程序中,一个变量的值会随着条件的不同而发生变化,并且当程序比较繁多时,某一个变量的属性也很难记忆,因此在gdb调试过程中,可以通过一些命令实现查看变量的值或者数据类型的功能。(在查看数据这一功能的调试中使用实例6.1中的程序test.c为例)

在前面的断点调试过程中,已经接触到了检查数据功能的命令,如print命令用于显示此变量或表达式当前的值。下面我们介绍几个常用的检查数据信息的命令。

(1)显示表达式的值

常见的显示变量或表达式的值的命令有print命令和display命令。

þ       print命令

print命令用于打印变量或表达式的值,表达形式如下:

 

print 变量名/表达式

 

执行完此命令,会通过“$”显示出这是在调试过程中第几次使用print命令,也就是显示当前的序列号。“$”作为print命令的参数,表示给定序号的前一个序号,“$$”表示给定序号的向前第二个序号。表达形式如下:

 

//表示给定序号的前一个序号

print $

//表示给定序号向前第二个序号

print $$

 

例如,当前给定序号是9,那么“print $”表示序号为8时显示的数据,“print $$”表示序号为7时显示的数据。

print命令还可以用于对变量赋值,并且还可以打印内存中从某一部分开始的一块连续空间的内容,表达形式如下:

 

//对变量赋初值

print vari=7

//打印连续空间数据

print 开始表达式@要打印的连续空间大小

 

该命令的具体应用如图6.12所示,以test.c程序中的数组Data为例。

 

display命令

该命令用于显示表达式的值,与print命令不同的是,使用了该命令后,每当程序运行到断点处,都会显示表达式的值。效果如图6.13所示,在程序的第九行设置了断点,并且使用display命令显示了变量j、数组pData[j]和pData[j-1]的值,每当运行到断点处时,都可以观察到这三个变量的值的变化。

 

关于display命令显示表达式的值,还可以使用disable display命令设置要显示的表达式暂时无效,即在下一次运行到断点时,不显示此表达式的变量值。有暂时失效必然就会有重新使之有效的命令,恢复失效的命令为enable display。运行效果如图6.14所示。

 

使用display命令显示数据,方便观察每次经过断点时此变量的变化过程,更容易理解程序。然而当经过了几次显示后理解了变量的变化规律,就没有必要再经过断点时每次都显示这些变量的值,因此可以使用delete display命令删除指定的显示数据的序号,如图6.13中设置了三个display命令显示的数据,每次显示时都列有序号。undisplay命令也可以起到结束某个变量值的显示,与delete display命令作用相同。效果如图6.15所示。

 

(2)查看变量或函数的类型

检查表达式的信息,包括表达式的值和表达式的数据类型。使用whatis命令和ptype命令均可以显示某个表达式的数据类型,两者的区别在于whatis命令只可以显示数据类型,而ptype命令可以给出类型的定义,如类和结构体变量。运行效果如图6.16所示。

 

(3)修改变量的值

当程序中存在一个循环体,循环次数很大,而在调试的过程中需要观测循环变量等于某一较大值时的状态,如果逐次循环,需要浪费很多的时间,而且也没有必要,此时,可以使用set命令修改这个循环变量为需要的值。这是set命令除显示数据之外的另一功能。运行效果如图6.17所示。

 

(4)查看内存

在gdb中提供了查看内存的命令x,可以查看此内存地址中的值。命令x的使用形式如:

 

x/

 

n、f和u为查看内存命令的可选参数。addr为起始地址。

n代表一个正整数,表示显示内容的个数,就是说从当前地址向后显示几个地址的内容。

f代表输出的格式,在缺省的情况下,输出格式依赖于它的数据类型,但是可以依据情况改变输出格式。f表示的输出格式有如下几种:

x:十六进制整数格式

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

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

o:八进制整数格式

t:二进制整数格式

c:字符格式

f:浮点数格式

u代表从当前地址开始向后请求的字节数。若缺省,gdb会默认为是四个字节。当我们指定了字节长度后,gdb会从指定内存地址开始,读写指定字节,并把它当作一个值取出来。u表示的字节数有以下几种形式:

b:字节(byte)

h:双字节数值

w:四字节数值

g:八字节数值

这几个参数n、f、u和addr可以理解成,从addr地址开始以f格式显示n个u数值。

 

10.4、使用观察窗口以及命令

在使用观察窗口时,需要设置监视点,用于监视某个表达式或变量,当表达式或变量的值被读或被写时让程序断下。在gdb调试工具中,关于设置监测点有如下几种命令:

watch命令:为表达式(或变量)设置了一个监测点,用于监视被写的内容,一旦表达式值(或变量值)有变化时,立即停住程序。

rwatch命令:用于监视某个表达式(或变量)被读,当表达式值(或变量值)有变化时,就停住程序。

awatch命令:用于当表达式(或变量)的值被读或被写时,停住程序。

info watchpoints命令:用于列出当前所设置了的所有监测点的相关信息。

通过上述介绍,可以了解到使用watch命令观察一个变量或者表达式值,当值改变,不满足watch命令中写入的条件时,即值有变化时,会停住程序,方便程序员观察此时的程序动态。

 

10.5、检查栈信息功能及其命令

栈是一种有限定性的线性表,在内存中有特定的一段连续空间。当程序调用了一个函数时,函数的地址、函数参数、函数内的局部变量都被压入栈中,保存在栈中。栈上的内容只在函数的范围内存中,在函数运行结束,这些内容也会被销毁。可以通过gdb调试命令查看栈信息。所谓的栈层信息是指栈的层编号、当前的函数名、函数参数值、函数所在文件及行号,函数执行到的语句。

在gdb调试工具中,可以查看栈信息的命令有如下几种:

backtrace命令,简写形式为bt。用于打印当前的函数调用栈的所有信息。

backtrace n命令,简写形式为bt n命令。其中n若为正整数,代表只打印栈顶上n层的栈信息;若n为负整数时,表示只打印栈底下n层的栈信息。

frame n命令,简写形式为f n命令。其中n为从0开始的整数,表示栈中的层编号。该命令用于显示第n层栈的信息,若没有n值,此命令可用于显示当前栈层的信息。

up n命令实现的功能是向栈底方向移动n层,若没有n,则表示向栈底方向移动一层。由于在栈中,栈底位于内存的高地址区域,栈顶位于低地址区,因此用up命令名表示,反之使用down命令名,表示向栈顶方向移动n层。

info frame命令,简写形式为info f命令。在查看栈信息时,可以通过此命令实现显示更为详细的栈层信息,如:调用函数与被调用函数的地址,当前函数使用的编程语言,函数参数地址及值,局部变量的地址等等。

info args命令用于显示当前函数的参数名及值。

info locals命令用于显示当前函数局部变量及其值。

info catch命令用于显示出当前函数中的异常处理信息。

 

10.6、检查源代码功能及其命令

在使用gdb调试工具时,都需要在编译程序时加上-g参数,将源程序的信息编译到执行文件中,这样在调试的过程中,就可以使用gdb命令查看到源程序的相关内容。查看源代码的功能有如下几种:显示源代码、搜索源代码、查看源代码的所在路径以及查看源代码的内存等等。下面简单介绍一下查看源代码与源代码的内存信息的功能及其相应的命令。

(1)显示源代码

在显示源代码的功能中,可以实现查看某一行周围的源程序以及指定行号的代码内容等。list命令就是用于显示源代码,当在list命令后面加上不同的参数时,会有不同的含义,如:

list不加任何参数表示显示当前行后面的代码。

<+>:显示当前行号后面的代码。

<->:显示当前行号前面的代码。

:显示程序第n行周围的代码。

:显示函数名为function的功能函数代码。

:显示从第first行到第last行之间的代码。

<,last>:显示从当前行到last行之间的代码。

:显示文件名为filename的文件的第n行的代码。

:显示文件名为filename的文件中的函数名为function的函数的代码。

 

在默认的情况下,list命令一次会显示10行,当查看代码时,有时会觉得一次显示10行没有必要,因此可以通过下面两个命令设置显示的行数。如:

set listsize :count为显示的行数,使用此命令可以设置每一次显示源代码的行数。

show listsize:此命令可以查看当前显示源代码的行数的设置。

 

(2)查看源代码的内存

在gdb调试程序时,难免会遇到需要查看某一行代码所在的内存地址等信息,因此在gdb的强大调试功能中提供了info line命令查看程序在运行时,所指定的源代码的内存地址,info line命令后面跟的参数可以是行号,可以是函数名等。

当使用图形模式的调试工具进行调试时,会进入到最底层的汇编代码进行查看,调试,使用gdb调试必然也可以查看最底层的汇编代码,如使用disassemble命令可以查看源程序当前执行时的机器码,即汇编语言的代码。

 

10.6、改变程序的运行功能及其命令

在调试程序时,往往并不是每一条语句,每一种可能都要逐条执行,只有在需要观察变化的语句或者是程序块中进行调试,因此,在运行的过程中,可以根据需要在gdb中动态地改变程序正常的运行顺序,可以通过跳转语句或者修改变量的值等方式,改变程序的运行方向。这个调试功能使得调试过程更加简单,快速。(此功能下的演示程序使用实例6.1的test.c程序)

(1)set命令

在介绍查看数据的功能时,介绍了使用print命令和set命令改变变量的值,减少执行的次数,直接运行到变量值为某值时的语句。

U      注意:在使用set命令改变变量值时,若在程序中有一个变量为width,恰巧使用set命令改变width变量的值,则会出现错误提示,那是因为set width命令是gdb调试工具中的一个命令,为了避免此种情况,在使用set改变变量值时尽量使用set var命令后面加上为width赋值的表达式。

改变变量值可以改变程序的运行顺序,通常应用在循环语句中,提前走出循环或者查看循环中变量为某值时的状态。然而在想要从一个分支跳到另一个分支时,就无法使用set改变变量值的方法了,但是可以使用set的如下命令更改跳转执行的地址。如:

set $pc = 0x400531

在这里更改了执行的地址,可以跳转到指定地址的代码处,同样,在gdb中还提供了一个可以任意跳转的命令,例如jump命令。

 

(2)jump命令

jump命令可以任意跳转,打乱执行的顺序,其原理也是改变当前寄存器中的值。jump命令的使用方法如下:

jump    //line为文件file的行号,代表跳转到此行开始运行

jump      //addr为代码所在行的地址,代表跳转到地址为addr处的语句开始执行

在使用jump命令进行随意跳转时,会忽略正常运行顺序上的语句,因此这种跳转方式进行调试,运行出来的结果可能会出现错误或不是正确的输出结果。因此在使用jump命令调试时要谨慎。

 

(3)return命令

return命令用于快速返回一个函数的返回值,当调试进入一个功能函数中时,若没有必要将函数中的所有语句都执行,可以使用return命令,快速跳出这个函数,并带回函数的返回值,忽略函数中还没有执行到的语句。

使用return命令,还可以使函数返回一个指定表达式的值,应用方法如下:

return     //将表达式的值作为函数返回值

 

(4)call命令

call命令用于显示表达式的值,若表达式中是函数名,那么起到的作用就是强制跳转到该函数,并显示函数的返回值。若函数无返回值(void),那么就不显示。该命令的使用方法如下:

call       //显示表达式的值,或显示函数的返回值

在查看数据的功能中,介绍了一个print命令,可以实现显示表达式的值的功能,同样若表达式为函数名,则显示函数的返回值,若函数无返回值,则显示void。

 

十一、多线程程序的调试

如今,多线程已经被许多操作系统所支持,包括Window/NT和Linux系统。在Linux平台上的多线程设计,包括多任务程序的设计、并发程序设计、网络程序设计和数据共享等。Linux平台上的多线程遵循POSIX线程接口,称为为pthread。

多线程程序在Linux平台上应用广泛,可以使用gdb命令直接调试运行一个多线程程序。多线程程序通常存在很多潜在的错误,因此使用gdb调试多线程程序变得很复杂。在本书中并没有涉及到多线程的程序,因此在此不对多线程的复杂调试进行过多介绍,详细内容请查阅相关的资料。

你可能感兴趣的:(Linux自学笔记(一))