嵌入式校招_面试经验大全【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语言##面经#
全部评论
麻烦大家点赞、评论+收藏哦~多谢~
1 回复 分享
发布于 2023-06-12 09:06 广东
羽哥,你是我滴神!
1 回复 分享
发布于 2023-06-12 13:47 上海
😍
点赞 回复 分享
发布于 2023-06-12 11:38 陕西
讲解很细致哦
点赞 回复 分享
发布于 2023-06-12 13:44 广东
点赞 回复 分享
发布于 2023-06-13 13:19 广东

相关推荐

点赞 评论 收藏
分享
10-27 17:26
东北大学 Java
点赞 评论 收藏
分享
45 185 评论
分享
牛客网
牛客企业服务