常见嵌入式面试题之C语言篇 ——第1期

前言

本博客旨在分享学习过程中整理过的相关知识,主要涉及C语言、C++及嵌入式相关问题。若涉及引用未注明出处的部分请及时私信或评论。博客内容有误的部分也会及时勘正,最后如有幸得到转载请注明出处~谢谢支持!

第1期问题:

1.关键字static的作用

在C语言中,static主要用于定义全局静态变量、局部静态变量以及静态函数。
全局静态变量 :在全局数据区内分配内存,作用域只在定义的本文件内可见,生存周期直到程序运行结束。
局部静态变量:在全局数据区分配内存,作用域只在定义的本模块内可见,存周期直到程序运行结束。
静态函数:只能在本源文件中使用,如果在其他文件中存在同名函数,两者互不干扰。
在C++中新增两种作用:定义静态数据成员或静态函数成员。
静态数据成员必须在类外定义及初始化,静态函数不能访问非静态数据成员

2.关键字const是什么含意

浅显理解:只读
(1)const int a;
(2)int const a;
(3)const int *a;
(4)int * const a;
(5)int const * a const;
其中(1)(2)作用是一样,a是一个常整型数。(3)意味着a是一个指向常整型数的指针(整型数是不可修改的,但指针可以)。(4)的意思a是一个指向整型数的常指针(指针指向的整型数是可以修改的,但指针是不可修改的)。(5)中a是一个指向常整型数的常指针(指针指向的整型数是不可修改的,同时指针也是不可修改的)。
保护某些重要的地址和数据,给读代码的人传递信息,防止代码无意被修改
1). 关键字const的作用是为给读你代码的人传达非常有用的信息
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现

3.关键字volatile有什么含意,并给出三个不同的例子

答:不进行编译器优化,程序在每次用到这个变量时都会重新读取,而不是用保存在寄存器里的备份
1).并行设备的硬件寄存器(如:状态寄存器)
2).一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3).多线程应用中被几个任务共享的变量

1.一个参数既可以是const还可以是volatile吗?解释为什么。
volatile修饰符告诉complier变量值可以以任何不被程序明确指明的方式改变,最常见的例子就是外部端口的值,它的变化可以不用程序内的任何赋值语句就有可能改变的,这种变量就可以用volatile来修饰,complier不会优化掉它。 const修饰的变量在程序里面是不能改变的,但是可以被程序外的东西修改,比如外部端口的值,如果仅仅使用const,编译器可能会优化掉这些变量,加上volatile就不会有问题。
2.一个指针可以是volatile 吗?解释为什么。
因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:中断服务子程序修改一个指向一个buffer的指针时,必须用volatile来修饰指针。

4.引用与指针的区别是什么

引用必须被初始化,指针不必。
引用初始化以后不能被改变,指针可以改变所指的对象。
不存在指向空值的引用,但是存在指向空值的指针。
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。

5. .h头文件中的ifndef/define/endif 的作用

防止该头文件被重复引用。

6.全局变量和局部变量在内存的区别

全局变量储存在静态数据区,局部变量在堆栈中。
全局变量的生命周期是整个程序运行期间,局部变量的生命周期是声明该变量的函数区间。

7.数组与链表的区别

数组是一块连续的内存区间、大小固定,数据只能连续存储。
链表可以是一块连续的内存区间也可以是不连续的内存区间,大小不固定,数据可以随机存储。

8.堆栈溢出一般是由什么原因导致的

1)函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
2)动态申请空间使用之后没有释放。由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。
3)数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。
4)指针非法访问。指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。

9. C/C++中的内存分配

在C/C++中内存分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区。通常32位Linux内核虚拟地址空间划分03G为用户空间,34G为内核空间,Linux进程地址空间分布如下图所示:

栈区(stack):由编译器在需要的时候分配,函数执行时,形参及局部变量(不包括static声明的变量)分配在栈区,函数运行结束后,内存自动释放。属于动态内存分配
堆区(heap):存放进程运行中被动态分配的内存段,由程序员手动分配释放。如果申请的内存没有被释放,则程序运行结束由系统自动回收。在C语言中,通过malloc或calloc、realloc、free函数申请、释放内存。**属于动态内存分配
全局/静态存储区(.data段):已初始化的全局变量(包括全局静态变量)和局部静态变量,程序运行结束后由系统释放。属于静态内存分配。
常量存储区(.rodata段):包含全局常量、const修饰的全局变量及字符串常量,const修饰的局部变量仍然在栈区。属于静态内存分配。
代码段(.text段):存放程序的二进制代码。

补充:BSS段(Block Started by Symbol):存放程序中未初始化的全局变量及初始化为0的全局变量。属于静态内存分配。
动态链接库映射区:如果程序调用了动态链接库,则会该部分。该区域用于映射装载的动态链接库。
保留区:内存中受到保护而禁止访问的内存区域。

10.不能做switch()参数的数据类型

答:除浮点型(单精度float,双精度double)、字符串类型(string)以外均可。C/C++中支持byte,char,short,int,long,bool,整数类型和enum(枚举)类型,不支持float,double,string。

11.局部变量能否和全局变量重名

答:能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

12.如何引用一个已经定义过的全局变量

答 :可以用引用头文件的方式,也可以用extern关键字。如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

13.全局变量可不可以定义在可被多个.C文件包含的头文件中,为什么

答 :可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时链接不会出错。

14.static 全局变量、局部变量、函数与普通全局变量、局部变量、函数的区别

静态全局变量的作用域:只在定义该变量的源文件内有效
static函数与普通函数作用域不同,加上static只能在当前源文件说明和定义。
static全局变量与普通的全局变量区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。

15.什么是预编译,何时需要预编译

答:预编译又称为预处理,是做些代码文本的替换工作。#define宏定义的替换,条件编译等。

16.结构与联合有和区别

(1)结构和联合都是由多个不同的数据类型成员组成, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
(2)对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

17.请说出const与#define 相比,有何优点

答:const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者(const)进行类型安全检查。define只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试

18.简述数组与指针的区别

答:数组在静态存储区被创建(如全局数组),或者在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
指针可以随时指向任意类型的内存块,更加灵活。

19论述含参数的宏与函数的优缺点

1.函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
2.宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
3.宏定义时,可以是任何类型的数据。对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换。

20.访问固定的内存位置(Accessing fixed memory locations)

嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;//先取出地址的值付给指针
*ptr = 0xaa66; //再把指针解引用

总结

近期会整理嵌入式面试相关问题,包含C语言、C++以Linux操作系统。之后会陆续更新基于以上三部分内容中的重点问题进行深入解析与探讨。
如果博客内容有不严谨或错误的部分,请及时评论或私信,本人将第一时间进行补充与更正!
还请广大网友不吝赐教~共同加油!

猜你喜欢

转载自blog.csdn.net/weixin_44524004/article/details/111824585