首页/学习资料/大华嵌入式一面面经(上)/
大华嵌入式一面面经(上)
2024-04-18 17:57:51868浏览
大华嵌入式一面面经,个人经历回顾版

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.chelp.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(); // 错误,无法编译,因为printHellomain.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())
友情链接: