目录
4. 字符串排序问题,时间复杂度O(n),任意字符串的排序并统计重复的个数,例如输入:$-%-#aeartvDEtGD%!% (输出:!1#1$1%3-2D2E1G1a2e1r1t2v1)
4. 为什么在挥手阶段,客户端要等待2MSL时间 2(Maximun Segment Lifetime)
6.列举一下OSI协议的各种分层。说说你最熟悉的一层协议的功能
#### 语言基础
1. new 和 malloc的区别
(1).new 、delete 是操作符,可以重载,只能在C++ 中使用;malloc 申请的内存空间要用free 释放,而new 申请的内存空间要用delete 释放。
(2).malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。
(3).new可以调用对象的构造函数,对应的delete 调用相应的析构函数。
(4).malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数。
(5).new、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。
type* name = new type(content);//type是元素类型,content是元素内容,name是变量名
2. malloc的底层实现
(1).malloc函数的底层实现是操作系统有一个由可用内存块连接成的空闲链表。调用malloc时,它将遍历该链表寻找足够大的内存空间,将该块一分为二(一块与用户申请的大小相等,另一块为剩下来的碎片,会返回链表),调用free函数时,内存块重新连接回链表。
(2).若内存块过于琐碎无法满足用户需求,则操作系统会合并相邻的内存块.
3. 堆和栈的区别
数据结构的堆和栈 | 栈是一种先进后出的数据结构。堆是一种经过排序的树形数据结构(通常是二叉堆),每个结点都有一个值,根结点的值最小或最大,常用来实现优先队列,堆的存储是随意的。 |
C语言内存分配的堆和栈 | 栈是向下生长的,栈中分配函数参数和局部变量,其分配方式类似于数据结构中的栈。堆是向上生长的,堆中分配程序员申请的内存空间(一旦忘记释放会造成内存泄漏),其分配方式类似于数据结构中的链表。 |
3.1内存分区
(1).栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈,栈的内存地址生长方向与堆相反,由高到底,所以后定义的变量地址低于先定义的变量.
(2).堆由开发人员分配和释放,若开发人员不释放,程序结束时由操作系统回收,分配方式类似于链表.
(3).堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式;空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB.
(4).堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低.
(5).堆都是动态分配的;栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配;动态分配由alloca函数进行分配,栈的动态分配是由操作系统进行释放,无需程序员手工实现。
(6).分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。堆的效率比栈要低得多。
(7).存放内容不同。栈存放的内容:函数返回地址、相关参数、局部变量、寄存器内容。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
3.2数据结构中的堆与栈
(1).栈拥有“先进后出”的特性(First In Last Out),简称FILO;栈分顺序栈和链式栈两种。栈是一种线性结构,可以使用数组或链表(单向链表、双向链表或循环链表)作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。
(2).堆是一种的树形结构,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小顶堆(或小根堆),如果根节点最大,称之为大顶堆(或大根堆).堆的左右子结点没有大小的顺序。
LINK:https://blog.youkuaiyun.com/K346K346/article/details/80849966
4. static 关键词在c与c++中的用法有哪些 2
在 C 中 static 用来修饰局部静态变量和外部静态变量、函数。
而 C++中除了上述功能外,还用来定义类的成员变量和函数:即静态成员和静态成员函数。
「注意」:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。
5. c++ 多态 2
不同的子类对象调用"相同"的父类方法,产生不同的执行结果.
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数
6. c++ 如何实现虚函数 2
虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。
7. 为什么c++ 析构函数一般要写成虚函数 2
由于类的多态性,基类指针可以指向派生类的对象,如果删除该基类的指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全,造成内存泄漏。所以将析构函数声明为虚函数。在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生,要将基类的析构函数声明为虚函数。
举个例子:
#include <iostream>
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
//运行结果:
//Parent construct function
//Son construct function
//Parent destructor function
将基类的析构函数声明为虚函数:
#include <iostream>
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
virtual ~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
//运行结果:
//Parent construct function
//Son construct function
//Son destructor function
//Parent destructor function
8.STL 中的内存分配器如何实现 2
STL的分配器用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:
new运算分两个阶段:(1)调用::operator new配置内存;(2)调用对象构造函数构造对象内容
delete运算分两个阶段:(1)调用对象析构函数;(2)调用::operator delete释放内存
为了精密分工,STL allocator将两个阶段操作区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。
同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。
9. c++ 程序的内存分布 2
32bitCPU可寻址4G线性空间,每个进程都有各自独立的4G逻辑地址,其中0~3G是用户态空间,3~4G是内核空间,不同进程相同的逻辑地址会映射到不同的物理地址中。其逻辑地址其划分如下:
3G用户空间和1G内核空间
静态区域:
text segment(代码段):包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
data segment(数据段):存储程序中已初始化的全局变量和静态变量
bss segment:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量,对于未初始化的全局变量和静态变量,程序运行main之前时会统一清零。即未初始化的全局变量编译器会初始化为0
动态区域:
heap(堆): 当进程未调用malloc时是没有堆段的,只有调用malloc时采用分配一个堆,并且在程序运行过程中可以动态增加堆大小(移动break指针),从低地址向高地址增长。分配小内存时使用该区域。 堆的起始地址由mm_struct 结构体中的start_brk标识,结束地址由brk标识。
memory mapping segment(映射区):存储动态链接库等文件映射、申请大内存(malloc时调用mmap函数)
stack(栈):使用栈空间存储函数的返回地址、参数、局部变量、返回值,从高地址向低地址增长。在创建进程时会有一个最大栈大小,Linux可以通过ulimit命令指定。
10.由gcc编译的C语言程序占用的内存分为哪几个部分
栈区(stack) | 存放函数的参数、局部变量。 |
堆区(heap) | 提供程序员动态申请的内存空间。 |
全局(静态)区(static) | 存放全局变量和静态变量,初始化不为0的全局变量和静态变量、const型常量在一块区域(.data段),未初始化的、初始化为0的全局变量和静态变量在相邻的另一块区域(.bss段)。 |
程序代码区 | 存放函数体的二进制代码和字符串常量。 |
11.指针与引用的区别?
(1).指针是变量,存储的是地址;而引用跟原变量是同一个东西,是原变量的别名。
(2).指针有const;而引用没有。
(3).指针的值可以为NULL;而引用不行。
(4).非const指针可以改变;引用只能在定义时被初始化,之后不可改变。
(5).指针可以有多级,如二重指针;而引用只能有一级。
(6).指针和引用自增(++)的意义不一样,指针自增是地址增加,引用自增是原变量增加。
(7).sizeof指针和引用得到的大小不一样,sizeof(指针)得到的是指针本身的大小,sizeof(引用)得到的是原变量的大小。
#### 数据结构
1. 二分查找 2
Think:二分查找是对有序数组而言的,那么我们只需要拿中间的元素来跟目标数值比较,如果相等则查找完成;如果中间的元素小于目标数值,那么说明目标元素在右边;如果中间的元素大于目标数值,那么说明目标元素在左边。接着再拿右边或左边的中间元素来比较……如此循环直到找到目标元素/找不到退出循环。时间复杂度为O(nlog2n)(二分思维)
int binary_search(int array[], int value, int size)
{
int low = 0;
int high = size -1;
int mid;
while(low <= high)
{
mid = (low + high) / 2; // 二分
if(array[mid] == value) // 中间数据是目标数据
return mid;
else if(array[mid] < value) // 中间数据比目标数据小
low = mid + 1;
else // 中间数据比目标数据大
high = mid - 1;
}
return -1;
}
2. 各种排序算法的时间,空间复杂度 2
排序方法 | 平均情况 | 时间复杂度 | 空间复杂度 | |
最坏情况 | 最好情况 | |||
直接插入排序 | O(n2) | O(n2) | O(n) | O(1) |
希尔排序 | O(n1.3) | O(1) | ||
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) |
快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(log2n) |
直接选择排序 | O(n2) | O(n2) | O(n2) | O(1) |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) |
3. 快速排序的基本思想 2
Think: 数组一开始是乱序的,以第一个元素为基准,先将其保存;指针1从后往前寻<=基准的元素,插在基准元素的位置,接着指针2从前往后寻找>基准的元素,插在指针1的位置上;指针1继续往前寻找<=基准的元素,插在指针2的位置上,接着指针2继续往后寻找>基准的元素,插在指针1的位置上;如此循环直到两指针相遇,将基准元素插在相遇的位置,至此,<=基准的元素都在基准元素左边,>基准的元素都在基准元素右边,再递归地对左边的元素进行相同的操作,然后递归地对右边的元素进行相同的操作,即可完成排序,时间复杂度为O(nlog2n)。(二分思维、递归思维)
void quick_sort(int *num, int start_num, int end_num)
{
if(start_num < end_num)
{
int i = start_num;
int j = end_num;
int temp = num[start_num]; // 以第一个元素为基准
while(i < j)
{
while(i < j && num[j] > temp) // 从后往前寻找比基准小的元素
j--;
if(i < j)
num[i++] = num[j]; // 插在基准元素前面的空位上
while(i < j && num[i] <= temp) // 从前往后找比基准大的元素
i++;
if(i < j)
num[j--] = num[i]; // 插在基准元素后面的空位上
}
num[i] = temp; // 基准元素归位
quick_sort(num, start_num, i - 1); //递归地对基准元素左边的数据排序
quick_sort(num, i + 1, end_num); //递归地对基准元素右边的数据排序
}
}
4. 字符串排序问题,时间复杂度O(n),任意字符串的排序并统计重复的个数,例如输入:$-%-#aeartvDEtGD%!% (输出:!1#1$1%3-2D2E1G1a2e1r1t2v1)
Think:字符的ASCII码总共只有128个(0~127),那么我们是不是可以建立一个长度为128的数组并初始化为0,然后遍历每个字符,并以字符的ASCII为下标将该元素+1。这样一来,只需要遍历一遍字符串,就可以将字符串排好序并统计好个数。接下来在遍历一遍这个数组,将对应的字符和个数打印出来即可。(哈希表思维,将元素的值与其存储位置关联起来)
#include <stdio.h>
#include <string.h>
void sort(char a[], int len)
{
int i;
char b[128] = {0}, key;
for(i = 0; i < len; i++)
{
key = a[i]; // 将字符对应的ASCII码作为下标
b[key]++; // 对应元素+1
}
for(i = 0; i < 128; i++)
{
if(b[i] != 0)
printf("%c%d", i, b[i]); // 按顺序打印出字符跟个数
}
printf("\n");
}
5. 单链表头插法和尾插法的区别 2
这个我没整(sorry:(
#### 操作系统
1. 进程,线程的区别,以及优缺点 2
(1).进程是系统中程序执行和资源分配的基本单位,线程是CPU调度的基本单位。[进程是资源管理的最小单位 线程是系统调度的最小单位]
(2).一个进程个拥有多个线程,线程可以访问其所属进程地址空间和系统资源(数据段、已经打开的文件、I/O设备等),同时也拥有自己的堆栈。
(3).同一进程中的多个线程可以共享同一地址空间,因此它们之间的通信实现也比较简单,而且切换开销小、创建和消亡的开销也小。而进程间的通信则比较麻烦,而且进程切换开销、进程创建和消亡的开销也比较大。
2. 进程的状态有哪些 1
(1).就绪态:所有运行条件已就绪,只要得到了CPU时间就可运行。
(2).运行态:得到CPU时间正在运行。
(3).僵尸态:进程已经结束了但父进程还没来得及回收。
(4).等待态:包括浅度睡眠跟深度睡眠。进程在等待某种条件,条件成熟后即进入就绪态。浅度睡眠时进程可以被信号唤醒,但深度睡眠时必须等到条件成熟后才能结束睡眠状态。
(5).暂停态:暂时停止参与CPU调度(即使条件成熟),可以恢复。
3. 线程的同步方式有哪些 2
信号、信号量、互斥锁、条件变量、自旋锁、读写锁
4. 进程间通讯的方式有哪些,各有什么优缺点 2
管道 (pipe) | 是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用,通常是父子进程。 |
有名管道 (named pipe) | 也是半双工的通信方式,但是它允许用于无亲缘关系的进程之间的通信。 |
信号量 (semophore) | 是一个计数器,通常作为一种同步机制,用于进程和线程间的同步。 |
消息队列 (message queue) | 是一个消息链表,存放在内核中并且由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限的缺点。 |
共享内存 (shared memory) | 一段能够被多个进程共同访问的内存,由一个进程创建。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而设计的,往往与其他通信方式如信号量配合使用,来实现进程间同步与通信。 |
套接字(socket) | 可用于不同主机间的进程通信。 |
信号(signal) | 用于通知接收进程某个事件已经发生,是一种比较复杂的通信方式。 |
6. 中断的大致流程 2
CPU在执行当前程序时,由于系统出现了某种需要处理的紧急情况,CPU暂停正在执行的程序,转而去执行另一段特殊程序来处理的出现的紧急事务,处理结束后CPU自动返回到原先暂停的程序中去继续执行,这种执行过程由于外界的的原因被中间打断的情况成为中断。
//7. 常见的汇编指令说一下 2
这个我没整明白(sorry:(
8. 函数调用的过程 2
#### 网络
1. TCP 和 UDP 的区别以及应用场景 1
(1)TCP是面向连接的,UDP是面向无连接的。
(2)TCP是面向字节流的,UDP是基于数据报的。
(3)TCP提供可靠服务(正确性、顺序性),UDP提供不可靠服务。
(4)TCP程序结构复杂,占用资源多;UDP程序结构简单,占用资源少。
(5)TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低,适合于实时应用,如IP电话、实时视频会议。
(6)TCP只支持一对一;UDP支持一对一、一对多、多对一、多对多
2. TCP 为什么可靠 2
因为TCP传输的数据满足四大条件:不出错、不丢失、不重复、不乱序,而且拥有窗口机制、拥塞控制机制来提高传输效率
3. TCP 三次握手,四次挥手 2
(1).第一次握手:客户端创建传输控制块,然后向服务器发出连接请求报文(将标志位SYN置1,随机产生一个序列号seq=x),接着进入SYN-SENT状态。
(2).第二次握手:服务器收到请求报文后由SYN=1得到客户端请求建立连接,回复一个确认报文(将标志位SYN和ACK都置1,ack=x+1,随机产生一个序列号seq=y),接着进入SYN-RCVD状态。此时操作系统为该TCP连接分配TCP缓存和变量。
(3).第三次握手:客户端收到确认报文后,检查ACK是否为x+1,ACK是否为1,是则发送确认报文(将标志位ACK置1,ACK=y+1,序列号SEQ=x+1),此时操作系统为该TCP连接分配TCP缓存和变量。服务器收到确认报文并检查无误后则连接建立成功,两者都进入ESTABLISHED状态,完成三次握手。
[ 注 意 : ]
(1).第一次挥手:客户端发出连接释放报文(FIN=1,seq=u),进入FIN-WAIT-1状态。
(2).第二次挥手:服务器收到连接释放报文后发出确认报文(ACK=1,ack=u+1,seq=v),进入CLOSE-WAIT状态。这时客户端向服务器方向的连接就释放了,这个连接处于半关闭状态,服务器还可继续发送数据。
(3).中间状态:客户端收到服务器的确认报文后,进入FIN-WAIT-2状态,等待服务器发送连接释放报文,此时仍要 接 收 数 据。
(4).第三次挥手:服务器最后的数据发送完,向客户端发送连接释放报文(FIN=1,ACK=1,ack=u+1,seq=w),进入LAST-ACK状态。
(5).第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认报文(ACK=1,ack=w+1,seq=u+1),进入TIME-WAIT状态。注意此时连接还未释放,必须进过2*MSL(最长报文寿命)的时间,客户端撤销相应的TCB(传输控制块的数据结构)后,才进入CLOSED状态。服务器一旦收到确认报文,立即进入CLOSED状态。
4. 为什么在挥手阶段,客户端要等待2MSL时间 2(Maximun Segment Lifetime)
(1).保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,服务器等待2*MSL后,会重新发送连接释放报文。等待2*MSL保证如果确认报文丢失客户端可以收到重传的连接释放报文,然后再次发送确认报文。
(2).使本连接持续的时间内所产生的所有报文段都从网络中消失,不会出现类似“两次握手”的意外情况。
//5. 常见的短距离传输协议
//XIAN HUI JIA ZAI SHUO
6.列举一下OSI协议的各种分层。说说你最熟悉的一层协议的功能
(1).七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
(2).五层划分为:应用层、传输层、网络层、数据链路层、物理层。
(3).四层划分为:应用层、传输层、网络层、网络接口层。(TCP/IP协议对应模型)
(4).各层功能:
应用层 | 在实现多个应用进程相互通信的同时,完成一系列业务处理所需的服务,比如电子邮件、文件传输、远程登录等。 |
传输层 | 为通信双方的主机提供端到端的服务,有两个不同的传输协议TCP和UDP,TCP提供可靠交付,而UDP并不能保证可靠交付。 |
网络层 | 处理分组在网络中的活动,例如分组的选路。 |
网络接口层 | 处理与电缆(或其他任何传输媒介)的物理接口细节。 |
7.TCP/IP协议包括
应用层 | Telnet(远程登录服务)、FTP(文件传输,使用TCP)、SMTP(建立于FTP上的邮件服务)、DNS(域名与IP地址相互转换)等 |
传输层 | UDP(无连接、不可靠)、TCP(面向连接、可靠传输) |
网络层 | IP(为主机提供一种无连接、不可靠、尽力而为的数据服务)、ICMP(主机与路由器之间传递控制信息)、IGMP(主机与路由器之间进行组播成员信息交互) |
网络接口层 | ARP(IP 地址-> MAC地址)、RARP(MAC地址 -> IP地址)等 |
8.socket编程的流程
(1)服务器端流程:
函数 | 作用 |
socket() | 创建套接字 |
bind() | 绑定本地IP地址和端口号 |
listen() | 设置监听队列长度 |
accept() | 等待连接 |
read() | 接收信息 |
close() | 关闭套接字 |
(2)客户端流程:
函数 | 作用 |
socket() | 创建套接字 |
connect() | 发送连接请求 |
write() | 发送信息 |
close() | 关闭套接字 |
9.硬链接与软链接的区别
(1)链接:是给系统中已有的某个文件指定另外一个可用于访问它的名称,链接也可以指向目录。即使我们删除这个链接,也不会破坏原来的文件或目录。
(2)硬链接:引用的是文件在文件系统中的物理索引(inode),当你移动或删除原文件时,硬链接不会被破坏。硬链接只能引用同一文件系统中的文件。
(3)软链接:就是新建一个文件(inode),这个文件专门用来指向别的文件(类似Windows中的快捷方式),若移动或删除原文件,则相应的软链接不可用。软链接可以跨文件系统,也可以对一个不存在的文件名或目录名进行链接。
【tips:】
readlef查看可执行文件是否带有调试功能、“(gdb)”为GDB内部命令引导符、back trace的命令,查看函数的调用的栈帧和层级关系