1、static关键字了解么,它的作用是什么?
1、static修饰局部变量时:①对其存储位置进行改变,存储在静态区;②改变其生命周期,为整个源程序,因此它只被初始化一次,并且被声明为静态的变量在这一函数被调用过程中维持其值不变。
2、static修饰全局变量时:改变其作用域,在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问,它是一个本地的全局变量(只能被当前文件使用)。
3、static修饰函数时:改变其作用域,一个被声明为静态的函数只可被这一模块内的其它函数调用。即,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)
修饰局部变量实例:
#include <stdio.h>
void nonStaticVarFunction() {
// 没有使用static修饰的局部变量
int var = 0;
var++;
printf("Non-static var: %d\n", var); // 输出的值总是 1
}
void staticVarFunction() {
// 使用static修饰的局部变量
static int staticVar = 0;
staticVar++;
printf("Static var: %d\n", staticVar); // 输出的值会递增
}
int main() {
for (int i = 0; i < 3; i++) {
nonStaticVarFunction();
}
for (int i = 0; i < 3; i++) {
staticVarFunction();
}
return 0;
}
输出结果:
Non-static var: 1
Non-static var: 1
Non-static var: 1
Static var: 1
Static var: 2
Static var: 3
解析:这个例子展示了没有使用
static修饰的局部变量
var每次调用
nonStaticVarFunction函数时都会被重新初始化为0,然后增加1并打印出来,因此输出总是1。而
static修饰的局部变量
staticVar在第一次调用
staticVarFunction函数时初始化为0,之后每次调用函数都会保留上次调用结束时的值,并继续递增,所以输出是递增的。这说明了
static`修饰符对局部变量生命周期的影响,使变量跨函数调用保持状态。
修饰函数实例:假设我们有两个源文件:main.c
和help.c
。我们在help.c
中定义了一个static
函数,这意味着这个函数只能在help.c
中被调用,尝试在main.c
中调用它将导致链接错误。
#include <stdio.h>
// static修饰的函数,仅在本文件内可见
static void printHello() {
printf("Hello from helper.c\n");
}
// 全局函数,可以被其他文件调用
void callPrintHello() {
printHello();
}
输出结果:
#include <stdio.h>
// 函数声明
void callPrintHello();
int main() {
// 调用helper.c中定义的函数
callPrintHello(); // 正确,因为callPrintHello是全局的
// printHello(); // 错误,无法编译,因为printHello在main.c中不可见
return 0;
}
解析:在这个例子中,尽管main.c
尝试调用printHello
函数将失败,但我们可以通过调用callPrintHello
间接调用printHello
。这是因为printHello
函数是静态的,它的作用域被限制在help.c
文件内。这样的设计提高了程序的模块化。
2、select的作用是什么,它和epoll的区别?
select函数的原理:
1、文件描述符的数量:单个进程监视的文件描述符的数量有上限(通常由FD_SETSIZE宏定义,经常是1024)。
2、处理机制、效率:select采用轮询的方式对文件描述符进行扫描,由于需要对所有的文件描述符fd进行遍历),文件描述符越多,效率越低。
3、内核、用户空间内存拷贝:由于select每次都会改变内核中的句柄数据结构集(fd集合),因此每次调用select都需要从用户空间向内核空间复制所有的句柄数据结构(fd集合),开销比较大
4、触发方式:select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次调用select还是会将这些文件描述符通知进程。
优点:
1、select的可移植性比较好,几乎在所有系统上都有支持
2、select可设置的监听时间timeout精度更好,可精确到微妙
缺点:
1、监听的文件描述符数量存在上限1024,不能根据用户需求进行更改
2、select需要在内核和用户空间之间复制整个文件描述符,开销较大
3、轮询的处理方式,当文件描述符数量很大时效率较低
epoll的原理及优势:
1、处理效率更高:epoll能够调用epoll_wait不断轮询就绪链表,期间可能多次睡眠和唤醒交替,但是它是在设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进行睡眠的进程。select需要遍历整个fd集合,而epoll只需判断就绪链表是否为空即可,节省了大量CPU的时间。这就是回调机制带来的性能提升。
2、节省开销:select在每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把 current 往设备等待队列中挂一次,而 epoll 只要一次拷贝,而且把 current 往等待队列上挂也只挂一次(在 epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个 epoll 内部定义的等待队列),这节省不少的开销。
3、map与set了解么,有什么区别
map和set是C++标准库(STL)中的两种重要的容器类型,其底层实现都是红黑树
map的原理:
1、map是一个关联容器,它存储的是键值对(key-value pair),其中每个键都是唯一的,而且每个键都映射到一个值。map
允许根据键来快速检索、删除或修改对应的值。
set的原理:
1、set也是一个关联容器,但它仅存储唯一的键(或元素),没有“值”的概念,每个元素都是唯一的。set主要用于快速检查一个元素是否存在于集合中。
两者区别:
1、存储内容:map存储的是键值对,每一个键映射到一个特定的值,而set仅存储值(或称为元素),并不关联任何值
2、用途:map支持下标操作,而set不支持下标操作,如果你需要建立一个元素到另一个元素的映射关系,应使用map;如果你只需要维护一个元素集合来快速检查某个元素是否存在,使用set更合适
4、GDB调试的基本操作,以及如何去追踪变量、查看堆栈信息
编译程序以便于调试
gcc -g program.c -o program
启动GDB
gdb program
GDB的基本操作
1、使用run运行程序
(gdb) run
2、设置断点
(gdb) break main # 在main函数开始处设置断点
(gdb) break file.c:10 # 在file.c的第10行设置断点
3、继续执行(当程序在断点处停止后,使用continue命令继续执行
(gdb) continue
4、单步执行(使用step执行下一行代码)
(gdb) step
5、查看变量值(使用print命令查看变量值)
(gdb) print variableName
6、退出GDB
(gdb) quit
7、设置条件断点(如果想在变量达到特定值时停止程序,可以设置条件断点)
(gdb) break file.c:20 if variableName==value
8、追踪变量,观察点(GDB的watch命令允许你设置观察点,程序在变量值改变时会自动停下来
(gdb) watch variableName
9、查看堆栈信息(backtrace命令查看当前调用堆栈)
①
(gdb) backtrace
②
(gdb) backtrace full
backtrace full不仅显示函数调用的序列,还会显示每个栈中的局部变量和参数的值
③
(gdb) frame 2
使用frame命令能够切换到特定的栈帧中,可以查看在那个特定栈帧的上下文的变量值和状态。上面命令为切换到栈帧2,可以在这个栈帧的上下文执行命令。比如查看变量等
实例:
#include <stdio.h>
void print_hello(int times) {
for (int i = 0; i < times; i++) {
printf("Hello, world!\n");
}
}
void test() {
char *ptr = NULL;
*ptr = 10; // 故意造成段错误
}
int main() {
print_hello(3);
test();
return 0;
}
在这个程序中,test函数会导致一个段错误。你可以编译这个程序并使用GDB来调试它。当程序崩溃时,你可以使用backtrace命令来查看导致错误的函数调用序列。
通过查看堆栈信息,能够更容易地找到程序出错的地方以及程序执行的路径。
常用的GDB调试命令
gcc -g test.c -o test #编译程序以便于调试
gdb test #启动调试
help #查看命令帮助,具体命令查询在gdb中输入help+命令,简写h
run #重新开始运行文件,简写r
start #单步执行,运行程序,停在第一执行语句
list #查看原代码(list-n,从第n行开始查看代码。1ist+函数名:查看具体函数),简写1set#设置变量的值
next #单步调试((逐过程,函数直接执行),简写n
step #单步调试(逐语句:跳入自定义函数内部执行),简写sbacktrace #查看函数的调用的栈帧和层级关系,简写bt
frame #切换函数的栈帧,简写f
info #查看函数内部局部变量的数值,简写ifinish #结束当前函数,返回到函数调用点
continue #继续运行,简写c
print #打印值及地址,简写pquit #退出gdb ,简写q
break+num #在第num行设置断点,简写b
info breakpoints #查看当前设置的所有断点
delete breakpoints num #删除第num个断点,简写d
display #追踪查看具体变量值
undisplay #取消追踪观察变量
watch #被设置观察点的变量发生修改时,打印显示i
watch #显示观察点
enable breakpoints #启用断点
disable breakpoints #禁用断点
set fo11ow-fork-mode child #Makefile项目管理:选择跟踪父子进程(fork())