面试知识
C/C++ 题目
一个保险柜,密码是由 NOMONEY 组成的 7 位数字。每个字母对应的数字隐藏在下面的 10 进制加法算式中,相同的字母对应相同的数字,不同的字母对应不同的数字,数字范围 0-9,并且已知密码中没有 4。请解码密码。
1 | G I V E |
M = 1(因两四位数相加得五位数,最高位必为1)
G = 9(9 + 1 = 10,产生进位,满足五位数)
O = 0(千位相加:9 + 1 + 0 = 10,进位后万位为1)
E = 8(个位:6 + 6 = 12 → Y = 2,进位1)
Y = 6(由E = 8推导)
V = 5,R = 2(十位:7 + 5 + 进位1 = 13 → 进位1,E = 6)
I = 3(百位:8 + 进位1 = 9 → N = 9,但需调整后最终得出N = 2)
N = 7(实际验证后调整)
答案不对 题目错了
题目1
题目描述: 请用 C 语言写一个函数,实现如下功能:输入年、月、日,返回这一天是这一年的第几天?请注意算法的效率(空间时间)。
答案:
1 | int dayOfYear(int y, int m, int d) { |
题目2
题目描述: 什么样的代码是优美的?哪些好的习惯有助于提高代码质量?
答案: 优美的代码通常具有以下特点:
- 可读性:代码结构清晰,命名规范,注释充分。
- 简洁性:代码简洁,避免冗余,使用合适的数据结构和算法。
- 可维护性:模块化设计,易于理解和修改。
- 健壮性:代码能够处理异常情况,进行错误检查和处理。
提高代码质量的好习惯包括:
- 代码审查:定期进行代码审查,发现并修复潜在问题。
- 单元测试:编写单元测试,确保代码的正确性和稳定性。
- 持续重构:定期重构代码,保持代码的简洁和可维护性。
- 遵循编码规范:遵循团队或项目的编码规范,保持代码风格的一致性。
题目3
题目描述: C 和 C++ 有哪些主要区别?
答案: C 和 C++ 的主要区别包括:
- 面向过程 vs 面向对象:C 是面向过程的语言,而 C++ 支持面向对象编程。
- 类和对象:C++ 支持类和对象,可以进行封装、继承和多态。
- 标准模板库(STL):C++ 提供了标准模板库,支持容器、算法和迭代器。
- 异常处理:C++ 支持异常处理机制,而 C 使用错误码。
- 命名空间:C++ 支持命名空间,避免命名冲突。
- 构造函数和析构函数:C++ 支持构造函数和析构函数,进行资源管理。
题目4
题目描述: 请从下面三个问题任选一个(15 分) a) 请描述 C++ 中各种智能指针(shared_ptr、auto_ptr、unique_ptr、weak_ptr)的适用场景及注意事项。 b) 请简述一下 Linux 中几种多路复用机制(select/poll/epoll)的优缺点对比。
答案: a) C++ 中各种智能指针的适用场景及注意事项:
- shared_ptr:适用于多个指针需要共享所有权的场景。注意事项:循环引用可能导致内存泄漏。
- auto_ptr:已在 C++11 中被弃用,不推荐使用。
- unique_ptr:适用于独占所有权的场景。注意事项:不能被复制,只能被移动。
- weak_ptr:用于解决 shared_ptr 的循环引用问题。注意事项:需要与 shared_ptr 配合使用。
b) Linux 中几种多路复用机制的优缺点对比:
- select:优点:简单易用。缺点:文件描述符数量有限,性能较差。
- poll:优点:没有文件描述符数量限制。缺点:性能仍然较差,尤其是在大量文件描述符的情况下。
- epoll:优点:性能优异,支持大量文件描述符。缺点:实现复杂,需要内核支持。
题目5
题目描述: 翻译:When developing embedded software, you must consider the following:
- Understand the default compilation tool behavior and the target environment so that you appreciate the steps necessary to move from a debug or development build to a fully standalone production version of the application.
- Some C library functionality executes by using debug environment resources. If used, you must implement this functionality to make use of target hardware.
- The toolchain has no inherent knowledge of the memory map of any given target. You must tailor the image this functionality has no inherent knowledge of the memory map of the target hardware.
- An embedded application must perform some initialization, such as stack and heap initialization, before the application can be run. A complete initialization sequence requires code that you implement in addition to the Arm Compiler C library initialization routines.
答案: 在开发嵌入式软件时,必须考虑以下几点:
- 了解默认编译工具的行为和目标环境,以便理解从调试或开发版本迁移到完全独立的生产版本应用程序所需的步骤。
- 一些 C 库功能通过使用调试环境资源来执行。如果使用这些功能,您必须实现这些功能以利用目标硬件。
- 工具链对任何给定目标的内存映射没有固有的知识。您必须调整该功能对目标硬件内存映射的了解。
- 嵌入式应用程序必须执行一些初始化,例如堆栈和堆初始化,然后才能运行应用程序。完整的初始化序列需要您实现的代码,以补充 Arm 编译器 C 库初始化例程。
题目6
题目描述: 找出下面代码中所有的错误以及不合理的地方,并请说明原因。(20 分)
找出下面代码中所有的错误以及不合理的地方,并请说明原因。(20 分)
c
复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A
{
public:
A() { m_jCount = 0; }
~A() {}
private:
int m_jCount;
};
class B : public A
{
public:
B() : A() { m_jCount = 1; }
~B(){}
};
void main(int argc, char** argv)
{
A* a = new B();
FILE* f;
int n;
if (argc > 1)
n = argv[0];
char* stuff = new char[n];
f = fopen("c:\\abc\\aa.txt", "r");
fread(f, n, stuff);
n = strlen(stuff);
if(n < 10)
{
return -1;
}
memset(stuff, 0, 20, 10);
fwrite(f, 1, n, stuff);
delete stuff;
delete a;
cout << stuff;
return 0;
}
答案: 代码中的错误和不合理之处包括:
A* a = new B();:new B()应该使用new B,而不是new B()。int m;:变量m未初始化,可能导致未定义行为。if (argc > 1):如果argc小于等于 1,程序会直接返回 -1,没有进行任何处理。char* stuff = new char[n];:new char[n]应该使用new char[n+1],并在最后添加一个空字符\0。memset(stuff, 0, 20, 10);:memset的第三个参数应该是要设置的字节数,而不是重复次数。delete stuff;:delete应该使用delete[] stuff;,因为stuff是一个数组。cout << stuff;:stuff可能包含未初始化的内存,直接输出可能导致未定义行为。
这些错误和不合理之处需要修正以确保程序的正确性和稳定性。
Uboot 引导嵌入式Linux操作系统启动的大概顺序是?
第一阶段:ROM code(芯片厂商内置的代码)初始化一些基本的时钟,从选定的设备中去加载FSBL的代码,并启动FSBL代码
第二阶段:FSBL first stage boot loader 完成时钟的初始化,初始化DDR,从选定的设备中去加载SSBL的代码,并启动SSBL代码
第三阶段:从外部储存或者网络中加载linux系统,通过启动动画向用户反馈启动过程,启动linux内核
第四阶段:linux内核初始化,挂载根文件系统,启动用户空间的init程序
最后进入到linux用户空间
第一阶段:ROM Code(芯片厂商内置的代码)
- 比喻:就像你刚睁开眼睛,身体还在床上,但你已经开始动脑子思考下一步要做什么。
- 实际操作:计算机刚开机时,芯片里有一些预先写好的代码(ROM Code),这些代码会做一些最基本的事情,比如检查硬件是否正常,设置一些基本的时钟(就像你调整闹钟一样),然后从某个地方(比如硬盘或者USB)找到一个叫做FSBL(First Stage Boot Loader)的程序,并把它加载到内存里,然后开始运行这个程序。
第二阶段:FSBL(First Stage Boot Loader)
- 比喻:你从床上爬起来,开始穿衣服,准备出门。
- 实际操作:FSBL程序接手后,会做一些更具体的事情,比如初始化DDR(一种内存,就像你整理书包一样),设置更精确的时钟(就像你调整手表时间),然后从某个地方找到SSBL(Second Stage Boot Loader)的代码,加载到内存里,并开始运行SSBL。
第三阶段:加载Linux系统
- 比喻:你出门去学校,路上可能会看看风景,听听音乐,这些都是你在路上的“启动动画”。
- 实际操作:SSBL程序会从外部存储设备(比如硬盘)或者网络中找到Linux系统的代码,加载到内存里。在这个过程中,可能会有一些启动动画显示给用户,让用户知道计算机正在启动,就像你在路上看到的风景一样。最后,Linux内核(计算机的大脑)开始运行。
第四阶段:Linux内核初始化
- 比喻:你到了学校,开始上课,老师开始讲解课程内容。
- 实际操作:Linux内核开始运行后,会初始化各种系统功能,比如挂载根文件系统(就像老师准备好教材一样),然后启动用户空间的init程序(就像老师开始讲解课程内容)。这个init程序就像是计算机的“老师”,它会启动其他各种程序和服务,让计算机能够正常工作。
最后:进入Linux用户空间
- 比喻:你开始认真听讲,参与课堂活动。
- 实际操作:当init程序启动完成后,计算机就进入了用户空间,这意味着用户可以开始使用计算机了,就像你开始参与课堂活动一样。用户可以打开各种应用程序,进行各种操作。
堆和栈的区别
1、栈区是系统自动申请自动释放,且空间较小
2、堆区是需要手动申请,手动释放,空间较大
3、栈区常用来存放局部变量等
4、堆区常用来存放全局变量等
5、栈的空间是连续的,而堆不是
1. 栈区(Stack)
- 比喻:想象你有一个书桌,书桌上有一个固定的区域用来放书本和文具。每次你写作业的时候,就会在这个区域放一些书本、笔记本和铅笔。写完作业后,你就会把这些东西收起来,这个区域又变得干净了。
- 实际操作:
- 自动申请和释放:栈区是由系统自动管理的。当你在函数中定义局部变量(比如
int a = 5;)时,系统会自动在栈区分配一小块空间来存储这个变量。当函数执行完毕后,系统会自动清理这块空间。 - 空间较小:栈区的空间相对较小,通常只有几MB。
- xxxxxxxxxx #include <stdio.h>/* 快速排序:实现从小到大排序 */void quickSort(int arr[], int size){ subSort(arr, 0, size - 1);}void subSort(int arr[], int start, int end){ if (start < end) { int base = arr[start]; int low = start; int high = end + 1; while (1) { while (low < end && arr[++low] <= base) ; // 找到从前往后第1个比base大的元素 while (high > start && arr[–high] >= base) ; // 找到从后往前第1个比base小的元素 if (low < high) { // 交换low和high位置的元素 int temp = arr[low]; arr[low] = arr[high]; arr[high] = temp; } else { break; } } // 交换start和high索引位置上的元素 int temp1 = arr[start]; arr[start] = arr[high]; arr[high] = temp1; // 递归调用 subSort(arr, start, high - 1); // 前半段继续排序 subSort(arr, high + 1, end); // 后半段继续排序 }}int main(){ int arr[] = {23, 45, 2, 46, 77, 2, 99, -9, -32, 0, 66}; int size = sizeof(arr) / sizeof(int); // 遍历 for (int i = 0; i < size; i++) { printf(“%d “, arr[i]); } printf(“\n”); // 排序 quickSort(arr, size); // 遍历 for (int i = 0; i < size; i++) { printf(“%d “, arr[i]); } printf(“\n”); getchar(); return 0;}c:数据结构.md
- 自动申请和释放:栈区是由系统自动管理的。当你在函数中定义局部变量(比如
2. 堆区(Heap)
- 比喻:想象你有一个大仓库,这个仓库可以用来放各种各样的东西,比如家具、玩具、书籍等。你需要的时候可以随时往里面放东西,用完之后也可以随时拿出来。
- 实际操作:
- 手动申请和释放:堆区是由程序员手动管理的。当你需要一个较大的空间来存储数据(比如一个大的数组或者一个复杂的数据结构)时,你需要手动申请这块空间(比如用
malloc函数)。用完之后,也需要手动释放这块空间(比如用free函数)。 - 空间较大:堆区的空间相对较大,通常可以达到几百MB甚至更多。
- 用途:堆区常用来存放动态分配的变量,比如全局变量、动态数组等。
- 手动申请和释放:堆区是由程序员手动管理的。当你需要一个较大的空间来存储数据(比如一个大的数组或者一个复杂的数据结构)时,你需要手动申请这块空间(比如用
3. 栈区常用来存放局部变量
- 比喻:你在书桌上写作业,每次写作业都会用到一些书本和文具,这些就是局部变量。写完作业后,这些书本和文具就被收起来了。
- 实际操作:在函数中定义的变量(局部变量)通常存放在栈区。这些变量在函数执行期间有效,函数执行完毕后,这些变量就会被自动清理。
4. 堆区常用来存放全局变量等
- 比喻:你在仓库里放了一些家具和玩具,这些是你长期需要的东西,不会轻易拿出来。这些就是全局变量。
- 实际操作:全局变量和动态分配的变量(比如用
malloc分配的内存)通常存放在堆区。这些变量在程序的整个运行过程中都有效,直到你手动释放它们。
5. 栈的空间是连续的,而堆不是
- 比喻:你的书桌是一个固定的区域,所有的东西都放在一起,空间是连续的。而仓库里的东西可以放在不同的地方,空间是不连续的。
- 实际操作:
- 栈区:栈区的空间是连续的,系统会自动管理这块空间,确保每次分配和释放都是连续的。
- 堆区:堆区的空间是不连续的,因为堆区是由程序员手动管理的,每次申请和释放的空间可能在不同的位置。
什么是野指针,产后的原因是什么
1、野指针是指向位置是随机的且不正确的
2、产生原因 定义时未初始化,指向位置随机
或者在释放时没有指向null,从而指向垃圾内存
1. 什么是野指针?
比喻:想象你手里拿着一张地图,但地图上的地址是乱写的,或者地图根本就没有地址,你按照这个地图去寻找某个地方,很可能就会迷失方向,甚至走到一个完全错误的地方。
实际操作:
- 野指针:在编程中,指针是一个变量,它用来存储另一个变量的内存地址。如果一个指针指向了一个随机的、不正确的内存地址,或者指向了一个已经被释放的内存地址,这个指针就被称为“野指针”。
- 野指针的危害:野指针可能会导致程序访问到错误的内存区域,从而引发程序崩溃、数据损坏等不可预测的问题。
2. 产生原因
1. 定义时未初始化
比喻:你手里拿着一张地图,但地图上没有写地址,你不知道它指向哪里,所以你可能会走到一个完全错误的地方。
实际操作:
当你定义了一个指针变量,但没有给它一个具体的地址时,这个指针变量的值是随机的。比如:
1
int *ptr; // 没有初始化,ptr的值是随机的
这个随机的值可能指向一个完全错误的内存地址,这就是野指针。
2. 释放后未指向NULL
比喻:你手里拿着一张地图,地图上写了一个地址,但这个地址的房子已经被拆掉了。你按照这个地图去寻找,可能会走到一个空地,或者一个完全不同的地方。
实际操作:
当你用
malloc等函数分配了一块内存,并用一个指针变量指向它,然后释放了这块内存,但没有把指针变量设置为NULL,这个指针变量就会变成野指针。比如:1
2
3int *ptr = malloc(sizeof(int)); // 分配内存
free(ptr); // 释放内存
// 此时ptr仍然指向原来的内存地址,但这块内存已经被释放了如果你继续使用
ptr,它就会指向一个已经被释放的内存地址,这就是野指针。
如何避免野指针的问题
1. 初始化指针
建议:在定义指针时,始终初始化指针为NULL或指向一个有效的地址。
示例:
1 | int *ptr = NULL; // 初始化为NULL |
或者:
1 | int value = 10; |
解释:初始化为NULL可以让你在后续使用指针时,通过检查是否为NULL来避免使用未初始化的指针。
2. 释放内存后将指针置为NULL
建议:在释放动态分配的内存后,立即将指针置为NULL。
示例:
1 | int *ptr = malloc(sizeof(int)); |
解释:这样可以避免指针继续指向已经被释放的内存,从而防止野指针的产生。
3. 检查指针是否为NULL
建议:在使用指针之前,始终检查指针是否为NULL。
示例:
1 | int *ptr = malloc(sizeof(int)); |
解释:通过检查指针是否为NULL,可以避免对未初始化或已经释放的指针进行操作。
4. 使用智能指针(C++)
建议:如果你使用的是C++,尽量使用智能指针(如std::unique_ptr或std::shared_ptr)来管理动态内存。
示例:
1 |
|
解释:智能指针会自动管理内存的分配和释放,避免手动管理内存带来的风险。
5. 避免重复释放
建议:确保内存只被释放一次,避免重复释放。
示例:
1 | int *ptr = malloc(sizeof(int)); |
解释:通过将指针置为NULL,可以避免重复释放的问题。
6. 使用工具检测野指针
建议:使用内存检测工具(如valgrind)来检测野指针和内存泄漏问题。
示例:
1 | valgrind ./your_program |
解释:valgrind等工具可以检测内存访问错误,帮助你发现野指针问题。
总结
野指针:指向一个随机的、不正确的内存地址的指针。
产生原因:
- 定义时未初始化:指针变量没有被赋予一个具体的地址,它的值是随机的。
- 释放后未指向NULL:指针变量指向的内存被释放了,但指针变量没有被设置为
NULL,它仍然指向原来的内存地址。
dma有什么用
主要是起一个搬运数据的作用,搬运通道可以时从外设到寄存器,外设到外设,寄存器到外设。
官方回答:DMA是在专门的硬件( DMA)控制下,实现高速外设和主存储器之间自动成批交换数据尽量减少CPU干预的输入/输出操作方式。主要作用就是减少CPU的负担。
进程间通信的方法
管道(有名管道和无名管道)
消息队列
共享内存
信号
信号量
套接字
程序中的内存分配方法
连续分配,分段,分页,段页式


