嵌入式校招_面试经验大全【C语言】【2_关键字】
【以下内容,后续会不断更新补充!】
各位同学有想看某一个知识点详细解析的,也可以在评论区留言~
目录
【专栏一】嵌入式校招指南
作者机械硕士,从零开始自学嵌入式软件,21届秋招进入国内芯片大厂。
从自身转行经历来看,网上嵌入式学习路线的资料少之又少,大多千篇一律且复制粘贴。
而嵌入式入行门槛高,技能树要求多,学习难度非常大,没有有效的方法指导,很容易迷失方向,错过校招。
在此专栏分享我的校招从零开始转行经验,听我给你娓娓道来~
专栏链接 https://www.nowcoder.com/creation/manager/columnDetail/MWZkkj
1.专栏大纲&写在前面
2.转行概述
3.前期准备
4.自学教材推荐_基础知识
5.自学教材推荐_笔试准备
6.开发板&项目
7.简历
8.行业&公司
9.城市&岗位
10.消费&工业电子类公司
未完待续……
【专栏二】嵌入式校招_面试经验大全
嵌入式软件校招的常见问题,应付校招面试的速效救心丸,你值得拥有!
嵌入式的知识太多太杂,不知道面试经常问哪些? 书上说的知识点太抽象,没有一定的基础很难理解?
别怕,本专栏用通俗的语言和比喻,为你讲清楚!
包含C语言、计算机组成原理、操作系统、数据结构与算法及计算机网络等,详见大纲。
1.【C语言】【1_变量】https://www.nowcoder.com/discuss/491773863525679104
2.【C语言】【2_关键字】https://www.nowcoder.com/discuss/497562309628276736
3.【C语言】【3_数据结构&位运算】https://www.nowcoder.com/discuss/505894349847224320
二、关键字
【问】介绍一下static关键字的作用
【答】static可以修饰全局变量、局部变量和函数。分别有以下作用:
- 全局变量:使全局变量对外部文件不可见。(隐藏)
- 局部变量:使局部变量存储在全局静态区,只初始化一次,函数结束后可一直存在,可在函数内修改。(更改存储区域&延长生命周期)
- 修饰函数,表明函数的作用范围,仅当前.c文件内可被调用,对外不可见。(隐藏)
【解析】
- 全局变量
全局变量本身就存在于全局静态区,static关键字的作用是隐藏,更改全局变量的作用域。
为什么要隐藏呢?
有的是考虑到重名的因素,但我个人认为还有一点是比较重要的,那就是处于安全的考虑。
比如有一个c文件名为Trusty.c,里面定义了各种对于用户密码的操作,由一个名为安全小组的团队维护。
那么我们知道,用户密码肯定是存放在一个全局变量PassWord中,方便各个函数调用操作。
PassWord在这个Trusty.c可以随意被调用,因为是由安全小组内部维护的,可以保证安全性。
但是,如果另外模块得知了PassWord这个变量名,就可以随意调用。
那么缺乏保护措施的另一个模块,很容易泄漏PassWord的内容,导致最重要的密码被窃取!这是绝对绝对不可以的!
此时就可以在Trusty.c中定义一个静态全局变量来对外部隐藏PassWord变量啦。
static char PassWord[10] = "XiaoYu666";
- 局部变量
static修改局部变量主要是延长生命周期。
我们都知道,局部变量原本就有隐藏作用,仅对当前函数可见。
同时局部变量原本是存放在内存中的栈区,每次进入函数时创建,退出函数时销毁。
使用static修改局部变量后,将局部变量从栈区移至全局静态区,其生命周期为一直存在。
所以是更改存储区域导致的生命周期延长。
那么,问题来了。为什么要这么做呢?
举个栗子: 有一个局部变量LocalVar仅被某一个函数RepeatFunc调用。该函数会重复执行很多次,每次都会基于上次的结果,对LocalVar做修改,且保留LocalVar的值到下一次RepeatFunc执行。
一种简单粗暴的方法是直接将LocalVar定义为全局变量。但是这样做有两个隐患,一个是其他函数有可能调用并修改LocalVar从而导致运行结果出错;第二个是造成全局变量泛滥,对于大型代码工程来说是不可接受的。
因此,static修改的局部变量派上用场了。可以同时具有局部变量的隐藏特性,还具有长久的生命周期来保留上次的结果。
真棒呀~~
- 函数
我们首先要知道,编程规范中,建议每一个函数只负责执行一个动作,不要一个函数完成多个动作。这叫做低耦合。
那么,同修饰全局变量中的例子,用户设置、更改和读取密码的过程中,会使用一系列中间函数来完成一个大的功能如加密&解密,同时返回一些关键信息。
如果此时外部模块调用这部分中间函数,非法获取它的返回值,就会造成关键信息泄漏。
这个是不被允许的!因此我们可以使用static关键字来修饰中间函数,从而对外隐藏它们。
只留给外部模块固定的API接口函数(设置、更改和读取密码),这样就能从代码编写层面来保护啦~
————————————————————
【问】介绍一下const关键字的作用
【答】防止变量被意外地修改。const修饰的变量,只能在初始化时赋初值,其后不能通过该变量来修改。
【解析】
- 常量指针与指针常量
1)常量指针。指向常量的指针。
const在*的左边。指针指向的变量是常量,不可通过指针改变(可通过原始变量来更改)。
举个栗子:
#include <stdio.h> int main(void) { int Value = 2023; const int *ConstPtr = &Value; printf("%d\n",*ConstPtr); *ConstPtr = 2024; }
通过上面的例子,我们验证了指针指向的变量是常量,不可通过指针改变的特性。
下面来看隐含的另一个意思,即可通过原始变量来更改。
#include <stdio.h> int main(void) { int Value = 2023; const int *ConstPtr = &Value; printf("%d\n",*ConstPtr); //*Num1 = 2024; Value = 2024; printf("%d\n",*ConstPtr); }
通过上面的例子我们可以知道,虽然我们不能通过ConstPtr这个常量指针来更改Value的值,但是我们可以通过Value这个原始的变量来更改~因为Value本身并不是const类型的~
2)指针常量。指针类型的常量。
const在*的右边。而指针变量的值是地址,即该指针的指向不可变,不能再指向其他地址。
但是指针指向的变量的数值可以改变。
#include <stdio.h> int main(void) { int Value = 2023; int TmpValue = 2024; int *TmpPtr = &Value; int *const PtrConst = &Value; printf("%p\n",PtrConst); PtrConst = &TmpValue; printf("%p\n",PtrConst); }
3)羽你俗说
int Value = 2023;
const int *ConstPtr = &Value; //常量指针
int *const PtrConst = &Value; //指针常量
怎么区分这两个呢?
我们首先明确一个指针的基本概念:
Ptr是一个指针变量,所保存的值是一个地址。
加上取值符号*后,*Ptr表示的是该地址保存的值。
那么为了方便记忆,我们将上述两种指针定义中的int暂时忽略。即:
const *ConstPtr = &Value; //常量指针
*const PtrConst = &Value; //指针常量
现在我们来看const修饰的右边是个什么东西。
常量指针中,const修饰的是*ConstPtr。上面我们说过这个*ConstPtr表示一个值,那么const修饰的就是这个值。
也就是说这个值不能被修改。
指针常量中,const修饰的是PtrConst。上面我们说过指针的值是地址,那么const修饰的是该地址,也就是这个地址不能被修改。
这样是不是容易理解和记忆多了呢~~~
————————————————————
【问】介绍一下volatile关键字的作用
【答】volatile关键字告知编译器,所修饰的变量随时有可能被改变,因此程序每次需要存储或读取这个变量时,都直接从变量所在的内存地址中读取数据。
【解析】
- 定义
volatile是一种类型修饰符,表明该变量可能会被意想不到地改变(操作系统、硬件或者其它线程等),防止编译器对代码进行优化
- 功能
1.使编译器每次使用变量时不从寄存器中读取,而是从内存中重新获取
2.防止编译器调整操作volatile变量的指令顺序(无法避免CPU动态调度)
- 背景知识
1.通常情况下,为了提高运行速度和优化代码量,编译器可能优化读取和存储相关的代码。
2.程序运行中读取某一变量时,为提高存取速度,编译器优化时会先把变量读取到一个寄存器中,后续直接从该寄存器中取值(而非从内存中重新读取)。
- 应用场景
嵌入式系统程序员经常同硬件、中断、RTOS等打交道,这些都通常要求使用volatile变量。
1)硬件相关的寄存器(如:状态寄存器)
我们都知道,嵌入式软件之所以特别,就是因为要和硬件打交道。
现在有一个反映硬件状态的变量,由于外部因素改变了硬件状态从而修改了该变量的值。
那么此时该变量在内存中的数值和其在寄存器中的数值是不一致的。如果此时CPU需要读取该值,很可能因为编译器的优化而直接读取寄存器中的值,导致读取到的是"旧值"。
2)中断服务程序中可能会修改的变量
当变量在中断服务程序(ISR)中被修改后,而编译器判断主函数里面没有修改该变量,因此可能只会从该寄存器中读取变量的"旧值"。
3)多线程间共享的变量
当某一线程读取一个变量时,编译器优化时会先把该变量读取到寄存器中;后面再取变量值时,就直接从寄存器中取值;
当另外的线程改变了该值后,该寄存器的值不会相应改变。如果此时CPU需要读取该值,很可能读取到"旧值"。
- 羽你俗说
通俗地说,小明(CPU)把自己的个人资料(变量)记录在一张纸上(寄存器)。
我们知道,姓名、性别、出生年月这些基本不会改变的属性(非volatile变量),一次记录(从内存copy到寄存器)后每次都可以重复使用。
然而身高、体重等随时可能改变的数据(volatile变量),每次必须要现场测量(从内存地址读取)才可以,不然只会得到之前的"旧值"。
- 常见问题
1.一个参数既可以是const还可以是volatile吗?
可以。
用const和volatile同时修饰变量,表示这个变量在软件程序中是只读的,但是可以程序外部条件(硬件)变化下改变。
每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。
注意:const只是不允许程序代码改变某一变量,其在编译期发挥作用,它并没有实际地禁止某段内存的读写特性。
2.一个指针可以是volatile 吗?
可以。
表示该指针变量容易改变其所指向对象,比如一个指向当前堆栈栈顶的指针,就会随着堆栈的增长改变指向的对象。
————————————————————
【问】介绍一下extern关键字的作用
【答】修饰变量或函数,在当前文件引用另一个文件中定义的变量或者函数。
【解析】
- 定义与声明
在这里我们要首先介绍一个概念,就是定义与声明。
函数定义:包含一个函数的所有,包括函数名、返回值、传入参数、函数体(函数功能的具体实现代码)等。
函数声明。也称之为函数原型。将函数名、返回值、传入参数通知编译器,暂不报错,函数定义在后面的代码中。
C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。
但在实际开发中,有时调用的函数(Func)在定义当前函数(main)后面时,则需在当前函数前添加函数声明,见下图。
若不添加函数声明,则会出现
- 编译与链接
这部分涉及到代码的编译与链接,这里简单介绍一下,具体的看后续专门的文章。
首先我们要知道,CPU在执行代码时,遇到函数Func的时候,不是把Func的代码复制过来运行的。而是跳转到Func代码的存放地址去执行,执行完以后再回到之前函数的代码地址(上下文)。
那么编译器在编译C代码时,遇到调用全局变量和函数的地方,就会用全局变量和函数的地址来代替,从而生成最后的可执行文件。怎么找到全局变量和函数的地址呢?在本文件中查找他们定义的位置即可获取地址。
问题来了,如果全局变量和函数,不是在本文件定义的,但我又想要用到他们怎么办呢?
直接调用是肯定不行滴,会报错的~
这个时候就要用到extern关键字啦!在调用他们的函数前面声明时,添加extern关键字告知编译器去其他文件中找~
当然,他们可不能被static修饰哦~原因见static关键字介绍
————————————————————
【问】介绍一下typedef关键字的作用
【答】使用 typedef 关键字为复杂的数据类型另外定义一个简单的别名。可定义的类型为基本数据类型和自定义数据类型。
【解析】
- 功能
1.重定义一个易记且意义明确的新类型名。
查看标准库文件stdint.h,里面定义了很多这样的类型。
这样我们就不必再去思考short究竟是多少位的类型,直接使用int16_t,清晰明了~
2.简化一些比较复杂的类型声明
在使用某些自定义的数据结构时,常使用typedef来简化声明。
比如结构体PersonData,如果我们要定义一个这样的指向该结构体的指针,需要struct PersonalData *MyDataPtr;
而使用typedef后只需要DataPtr MyDataPtr即可。
- typedef和define的区别
原理不同:#define是预处理指令,预处理阶段;typedef是关键字,在编译阶段
功能不同:#define是替换,typedef是别名
连续定义变量时,typedef保证所有变量均为同一类型,而#define无法保证
————————————————————
更多内容,持续更新中!!!
【觉得有用的小伙伴们可以订阅一下专栏,后续还有更多文章哦~ 😀 】
作者其他专栏
【嵌入式校招指南_完整学习路线】https://www.nowcoder.com/creation/manager/columnDetail/MWZkkj
请帮忙点赞、评论+收藏,是对我最大的支持~感谢!!!
#校招##嵌入式##offer##C语言##面经#