【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目总结

【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目总结

【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目
另外:
【机试题】2019.8.4大疆嵌入式笔试题A卷
【机试题】2018大疆嵌入式笔试题A卷
【机试题】2014大疆嵌入式笔试题

  • 关键字volatile
           表示一个变量也许会被后台程序改变,关键字 volatile 是与 const 绝对对立的。它指示一个变量也许会被某种方式修改,这种方式按照正常程序流程分析是无法预知的(例如,一个变量也许会被一个中断服务程序所修改)。这个关键字使用下列语法定义:volatile data-definition。
           变量如果加了 volatile 修饰,则会从内存重新装载内容,而不是直接从寄存器拷贝内容。 volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
           volatile应用比较多的场合,在中断服务程序和cpu相关寄存器的定义。
//示例一
#include 
int main (void)
{
	int i = 10;
	int a = i; //优化
	int b = i;
 
	printf ("i = %d\n", b);
	return 0;
}
//示例二
#include 
int main (void)
{
	volatile int i = 10;
	int a = i; //未优化
	int b = i;
 
	printf ("i = %d\n", b);
	return 0;
}

       使用 volatile 的代码编译未优化。volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
       volatile 使用:1.并行设备的硬件寄存器(如:状态寄存器);2.一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);3.多线程应用中被几个任务共享的变量。
关键字volatile参考链接
关键字volatile参考链接

  • 关键字 inline
           大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义(注意是定义而非声明,下文继续讲到)的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开。
    关键字 inline参考链接
    关键字 inline参考链接
  • C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用
           volatile应该是在编译阶段,extern在链接阶段。
           volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。
  • 请你说一下源码到可执行文件的过程
           对于C++源文件,从文本到可执行文件一般需要四个过程:
    预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
    编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
    汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
    链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件
    在这里插入图片描述
    1)预编译
           主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下
    1、删除所有的#define,展开所有的宏定义。
    2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
    3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。
    4、删除所有的注释,“//”和“/**/”。
    5、保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。
    6、添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
    2)编译
           把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
    1、词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
    2、语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
    3、语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
    4、优化:源代码级别的一个优化过程。
    5、目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
    6、目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。
    3)汇编
           将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows下)、xxx.obj(Linux下)。
    4)链接
           将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接:
    1、静态链接:
           函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。
           空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本;
           更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
           运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
    2、动态链接:
           动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
           共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;
           更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。
           性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
  • sizeof
    1.如果是数组
    #include
    int main()
    {
    int a[5]={1,2,3,4,5};
    printf(“sizeof数组名=%d\n”,sizeof(a));
    printf(“sizeof *数组名=%d\n”,sizeof(*a));
    }
    运行结果
    sizeof数组名=20
    sizeof *数组名=4
    2.如果是指针,sizeof只会检测到是指针的类型,指针都是占用4个字节的空间(32位机)。
    char *p = “sadasdasd”;
    sizeof( p):4
    sizeof(*p):1//指向一个char类型的
    sizeof(数组名)与sizeof(*数组名)参考链接
    sizeof(指针)与sizeof(*指针)参考链接
  • struct与union数据内存对齐,内存对齐的作用。

在这里插入图片描述
       结构体struct内存对齐的3大规则:
1.对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍;
2.结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍;
3.如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型。

#pragma pack(1)
 
struct fun{
	int i;
	double d;
	char c;
};

       sizeof(fun) = 13

struct CAT_s
{
    int ld;
    char Color;
    unsigned short Age;
    char *Name;
    void(*Jump)(void);
}Garfield;

       按照上面的3大规则直接来进行分析:
1.使用32位编译,int占4, char 占1, unsigned short 占2,char* 占4,函数指针占4个,由于是32位编译是4字节对齐,所以该结构体占16个字节。(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是int是最长的字节,所以按4字节对齐);
2.使用64位编译 ,int占4, char 占1, unsigned short 占2,char* 占8,函数指针占8个,由于是64位编译是8字节对齐,(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是函数指针是最长的字节,所以按8字节对齐)所以该结构体占24个字节。

//64位
struct C 
{ 
	double t;   //8   1111 1111
	char b;  //1      1
	int a;   //4      0001111  
	short c;  //2     11000000
};  
 sizeof(C) = 24;  //注意:1 4 2 不能拼在一起

       联合体union内存对齐的2大规则:
1.找到占用字节最多的成员;
2.union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员.

//x64
typedef union {
    long i;
    int k[5];
    char c;
}D

       要计算union的大小,首先要找到占用字节最多的成员,本例中是long,占用8个字节,int k[5]中都是int类型,仍然是占用4个字节的,然后union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员,为了要容纳k(20个字节),就必须要保证是8的倍数的同时还要大于20个字节,所以是24个字节。
       内存对齐作用:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
union使用说明参考链接
union和struct中字节数参考链接
结构体的内存对齐图解参考链接

  • 结构体占用内存
    在32位系统中有如下定义,则sizeof(data_t)的值是()
    A.15 B.19 C.11 D.8
typedef struct_data{
	char m:3;
	char n:5;
	short s;
	union{
		int a;
		char b;
	};
   int h;
}_attribute_((packed)) data_t;

变量定义后接冒号解释参考链接
结构体的中含有位域参考链接

  • 片上系统中,常用来在片内做数据传输的总线是
    A. UART B .I2C C.AXI D SPI
    AXI总线参考链接

  • 几种总线接口的通信方式
    【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目总结_第1张图片

  • ARM指令和Thumb指令
    ARM指令和Thumb指令参考链接

  • 请简述linux或RTOS中,栈空间最大使用率和栈溢出检测方法。
           方法一:在任务切换时检测任务指针是否过界了,如果过界了,在任务切换的时候会触发栈溢出的钩子函数。
           方法二:任务创建的时候将任务栈所有的数据初始化为0xa5,任务切换时进行任务栈检测的时候会检测末尾的16个字节是否都是0xa5,通过这种方式来检测任务栈是否溢出了。

  • 抢占式内核中,任务调度实际有哪些
           Linux内核
    抢占式内核参考链接

  • 一个程序中含有以下代码块,请说明其中各个变量的生命周期,作用域和存储位置(提示:存储位置可以选泽 ‘text’ ‘data’ ‘bss’ ‘heap’ ‘stack’)

【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目总结_第2张图片
       一个程序本质上都是由BSS段、data段、text段(代码区)三个组成的。可以看到一个可执行程序在存储(没有调入内存)时分为代码段、数据区和未初始化数据区三部分。
       BSS段(未初始化数据区):通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。
       数据段:数据段也属于静态内存分配。该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
       代码段:存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量
       text段和data段在编译时已经分配了空间,而BSS段并不占用可执行文件的大小,它是由链接器来获取内存的。
        bss段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为0。需要存放在程序文件中的只有正文段和初始化数据段。
       data段(已经初始化的数据)则为数据分配空间,数据保存到目标文件中。
       数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段的后面。当这个内存进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。
       可执行程序在运行时又多出两个区域:栈区和堆区。
       栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
       堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。
【机试题】2019.8.6大疆嵌入式笔试题B卷笔试题目总结_第3张图片

int a0=1;
static int a1;
const static a2=0;
extern int a3;

void fun(void)
{
	int a4;
	volatile int a5;
	return;
}

a0 :全局初始化变量;生命周期为整个程序运行期间;作用域为所有文件;存储位置为data段。
a1 :全局静态未初始化变量;生命周期为整个程序运行期间;作用域为当前文件;储存位置为BSS段。
a2 :全局静态变量
a3 :全局初始化变量;其他同a0。
a4 :局部变量;生命周期为fun函数运行期间;作用域为fun函数内部;储存位置为栈。
a5 :局部易变变量;生命周期为
内存管理参考链接
变量的类型、作用域、存储空间、生命周期参考链接
static extern 局部变量 全局变量 生命周期 作用域
变量类型参考链接

  • 请用c语言直接编写以下四个宏
    1、ALGN_DOWN(x,a)将数值x按照a的整数倍向下取整,例如ALGN_DOWN(65,3)—>63
    2、ALGN_UP(x,a) 将数值x按照a的整数倍向上取整,例如ALGN_UP(65,3)—>66
    3、ALGN_2N_DOWN(x,a) 将数值x按照a的整数倍向下取整,a是2的n次幂,例如ALGN_2N_DOWN(65,4)—>64.
    4、ALGN_2N__UP(x,a) 将数值x按照a的整数倍向上取整,a是2的n次幂,例如ALGN_2N_UP(65,4)—>68.
    备注:数值位unsigned int;对2的n次幂的情况只要利用这个特性用更高效的方法实现。
    问题3.4执行运算符右移操作,右移就是原数除2
  • C语言编程
    已知某外设有两个32—bit的寄存器
    数据寄存器,内存映射地址为0x80000000
    状态寄存器,内存映射地址0x80000004
    状态寄存器的bit31为A功能的标志位
    请补充完成以下结构体描述两个寄存器,
    Typedef struct{
    ………
    }A_regs_t;
    请用c语言实现如下函数;
    /*
    *read A data if A status is 1,and then clear the A status bit.
    *@param[in] a_regs;pointer to registers of A function
    *@param[out] result;data read from register
    @retval 0:successs;<0:error code
    */
    Int32_t_get_A_result(A_regs_t a_regs,uint32 _tresult);
    并写出调用这个函数的代码。

你可能感兴趣的:(面试)