原文链接https://mp.weixin.qq.com/s/jh5-RVvCgpCIW_3vFiM2vg
第一部分:基本概念1.关键字static的作用(1)在函数体内:一个被声明为静态的变量,在这一函数被调用过程中,维持其值不变。(该变量值初始化一次) (2)在模块内,函数体外:一个被声明为静态的变量,可以被模块内所用函数访问,但不能被模块外其他函数访问。 (3)在模块内,一个被声明为静态的函数,该函数只能被这一模块内其他函数调用,其他模块无法调用。 2.引用与指针的区别:(1)引用必须初始化,指针不用 (2)引用初始化后不能修改,指针可以改变所指对象 (3)不存在指向空的引用,指针存在空值 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。 引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。 关-注-公-众-号:嵌入式基地 回-复:【面试】 获-取-面-试-资-料 3..h头文件中的#ifndef/#define/#endif作用:防止头文件被重复引用 4.#include <>与#include “”区别:<>是从系统指定的路径下寻找;””是先从当前路径下寻找 5.描述实时系统的基本特性:在特定时间内完成特定的任务,实时性与可靠性。 6.全局变量与局部变量是否存在区别?全局变量存在静态数据区,局部变量存在栈中 7.堆栈溢出一般是由于什么原因?(1)没有回收垃圾资源 (2)层次太深的递归调用 8.冒泡排序算法的时间复杂度O(n^2) 9.什么函数不能声明为虚函数?构造函数 10.队列与栈的区别:队列:先进先出;栈:先进后出 11.不能做switch()的参数类型实型 12.局部变量是否能和全局变量重名?可以。局部变量会屏蔽全局变量。若要使用全局变量,使用::。 在函数内引用该重名变量时,会使用同名的局部变量,而不会使用全局变量(就近原则)。 对一些编译器而言,同一函数内可以定义多个同名的局部变量,例如:在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在该循环内。 13.如果引用一个已经定义过的全局变量?可以包含其头文件,也可使用关键字extern。 引用头文件:通过引用头文件的方式来引用某个头文件中声明的变量,假如将该变量写错了,在编译阶段会报错。 Extern:通过extern的方式引用,若将变量写错,编译阶段编译器不会报错,在链接阶段会进行报错提示。 14.全局变量能不能定义在可被多个.c文件包含的头文件中?可以。在不同的C文件中以static的形式来声明同名全局变量。 可以在不同的C文件中声明同名的全局变量,前提是只能在一个C文件中对变量赋初值,此时链接不会报错。 15.for(;1;)有什么问题?什么意思?与while(1)相同,无限循环 16.do…while()与while…do有什么区别?前者循环一遍后再判断;后者先判断后循环 17.static全局变量、局部变量、函数与普通全局变量、局部变量、函数有什么区别?(1)static全局变量与非static全局变量区别: 存储方式上相同,都是静态存储。 作用域:非static全局变量的作用域在各个文件中都是有效的;static的全局变量作用域只限于当前源文件中,只初始化一次。 (2)static函数与普通函数作用域不同。Static函数只能在当前源文件中使用,而非Static函数可以在其他源文件中使用。 (3)static局部变量只初始化一次,下次调用使用上次的数值。 18.程序的内存分配C/C++编译的程序占用的内存分为以下几部分。 (1) 栈区,编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。 (2) 堆区,由程序员分配释放。若程序员没有进行资源回收,程序结束时,可能会由OS回收。与数据结构中的堆是两回事。 (3) 全局区(静态区),全局变量与静态变量是存储在一起的。初始化的全局变量与初始化的静态变量在一块区域,未初始化的全局变量与未初始化的静态变量存储在一起。程序结束后由OS释放 (4) 常量区,存储常量,字符串。程序结束后由OS回收。 (5) 程序代码区,存放函数体的二进制代码。 示例: int a = 0; //全局初始化区域char *p1; //全局未初始化区域int main(int argc, char const *argv[]) { int b; //栈 char s = "sss"; //栈 char *p2; //栈 char *p3 = "123456"; //p3在栈上,“123456\0”在常量区 static int c = 0; //全局初始化区域 p1 = (char *)malloc(10); //分配得到的区域在堆上 p2 = (char *)malloc(20); strcpy(p1, "123456"); //“123456\0”在常量区 return 0;}
19.堆和栈的区别(1)申请方式: 堆:由程序员申请,在c语言中使用malloc函数申请。(嵌入式单片机应用中不建议使用此函数)。 例如:p2 = (char *)malloc(10),p2本身是在栈上。 C++中,new运算符 栈:由系统分配。 (2)申请后系统响应: 栈:若当前栈的剩余空间大于申请空间,系统将会给程序提供内存;否则,将提示异常,栈溢出。 堆:操作系统有个记录空闲内存地址的链表,当系统收到程序的申请时,将会遍历该链表,寻找第一个内存空间大于所申请空间的堆节点,然后将该节点从链表中删除,并将该内存空间分配给程序。找到的堆节点的大小不一定正好等于申请的大小,此时系统会将多余的部分重新放入空闲链表中。对于大多数系统,这块内存空间的首地址会记录本次分配的大小,这样,代码中的delete语句才能正确释放该块内存空间。 (3)申请大小的限制: 栈:在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域(栈顶地址与容量是系统提前规定好的)。 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。堆的大小限制于系统中有效的虚拟内存。 (4)申请效率: 栈:由系统自动分配,速度快,但程序员无法操作。 堆:由new、malloc分配,速度慢,容易产生内存碎片,但使用方便。 Windows下最好使用virtual alloc分配内存,这块内存既不在堆,也不在栈,是直接在进程的地址空间中保留一块内存,速度快,灵活,使用不方便。 (5)堆和栈中的存储内容 栈:在函数调用时,是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数C语言编译器中,参数是由右往左入栈的,然后是函数中的局部变量。静态变量不入栈,在全局初始化区域。 当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。 堆:一般在堆的头部用一个字节存储堆的大小。堆中的具体内容由程序员分配。 (6)存取效率比较: Char s1[] = “ssssss”; Char *s2 = “bbbbbb”; aaaaaa是在运行时赋值的,bbbbbb是在编译时确定的。 但在以后的存取中,栈上的数组比指针指向的字符串(堆)块。 void main() { char a = 1; char c[] = "1234567890"; char *p = "1234567890"; a = c[1]; a = p[1]; return;}
对应的汇编代码 10:a=c[1]; 004010678A4DF1movcl,byteptr[ebp-0Fh] 0040106A884DFCmovbyteptr[ebp-4],cl 11:a=p[1];0040106D8B55ECmovedx,dwordptr[ebp-14h] 004010708A4201moval,byteptr[edx+1] 004010738845FCmovbyteptr[ebp-4],al
第一种:在读取时,直接将字符串中的元素读到寄存器c1中 第二种:先将指针读取到edx中,再根据edx读取字符。 20.什么是预编译,什么时候需要预编译?预编译又称为预处理,是做代码文本的替换工作,处理#开头的指令,比如拷贝#include包含的头文件代码;#define宏定义的替换。在程序开始编译之前进行。 C语言编译系统在对程序编译之前,先进行预处理。预处理主要提供以下功能: (1)宏定义(2)头文件包含(3)条件编译 21.关键字const含义const 只读 应用:
const int a; //常整型数 int const a; //常整型数 const int *a; //a是一个指向常整型数的指针(指针可以修改,整型数不可修改) int * const a; //a是一个指向整型数的常指针(指针不可以修改,整型数可以修改) const int * const a; //a是一个指向常整形数的常指针(都不可修改) 使用const关键字的理由: (1) 为读代码的人提供非常有用的信息,实际上应用一个参数为常量是为了告诉用户这个参数的应用目的。 (2) 通过给优化器一些附加信息,使用关键字const也许能够产生更紧凑的代码 (3) 合理使用const可以使编译器很自然的保护那些不希望被修改的参数,防止其被无意的代码修改。 22.关键字volatile含义?应用例子:含义: 一个被定义为volatile的变量是说这个变量可能会被意想不到的改变,这样编译器就不会去假设这个变量的值了。准确的说,优化器在用到这个变量时,必须每次都小心点重新读取这个变量的值,而不是直接使用寄存器中的备份值。 应用:
(1)并行设备的硬件寄存器 (2)一个中断服务子程序中会访问到的非自动变量。 (3)多线程应用中被几个任务共享的变量 其他问题: 1. 一个参数既可以是const还可以是volatile吗?为什么? 2. 一个指针可以是volatile吗 3. 下面函数有什么错误 Int square(volatile int *ptr) { Return *ptr * *ptr; } (1) 可以,一个例子为只读的状态寄存器。它是volatile是应为他可能被意想不到的改变。它是const是应为程序不因该去修改它。 (2) 是的,虽然不常见。一个例子为中断服务子程序修改一个指向一个buffer的指针时。 (3) 这段代码的目的是为了返回指针*ptr的平方,但是由于*ptr是一个指向volatile类型的参数,因此编译器将产生类似于下面的代码: int square(volatile int *ptr) { int a, b; A = *ptr; B = *ptr; Return a * b;}
由于*ptr的值可能被意想不到的改变,因此,a与b的值可能不同,结果这段代码返回的结果与预期可能并不相同。 正确代码: int square(volatile int *ptr) { int a; A = *ptr; Return a * a;}
23.三种基本数据模型按照数据结构类型的不同,将该数据模型划分为层次模型、网状模型、关系模型 24.结构体与联合体有什么区别 共用体,允许在相同的内存地址存储不同的数据类型;可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同内存地址的方式 共用体,占用的内存应该足够存储共用体中最大的成员。 结构体,占用内存是内部所有变量所占空间之和。结构体,每个成员变量都有自己的内存地址。 25.#define与const区别:1.const有数据类型,宏定义没有数据类型;编译器可以对前者进行安全检查,对于后者不能进行安全检查,只能进行字符替换。 2.有些调试工具可以对const进行调试,而宏定义无法调试。 3.const定义的常量有作用域,而#define不重视作用域,默认定义处到文件结尾。 26.数组与指针区别:数组要么在静态数据区被创建,要么在栈上被创建。 指针可以随时指向任意类型的内存块。 (1)修改内容上的差别:
char a[] = “hello”; A[0] = ‘x’; Char *p = “world”;//p指向的是常量字符串 P[0] = ‘x’;//编译器无法发现该错误,运行时会报错 (2)使用sizeof()运算符计算容量: 数组可以使用sizeof计算出容量,而sizeof(p)计算得到的是一个指针变量的字节数,一般为4个字节,而不是p所指向的内存容量。 注意:当数组作为函数形参进行传递时,该数组自动退化为同类型指针: void TestBufferSize1(char a[]) { printf("buffer size = %d \r\n", sizeof(a));}void TestBufferSize2(char a[6]) { printf("buffer size = %d \r\n", sizeof(a));}int main(){ char b[6] = "12345"; printf("b size = %d \r\n", sizeof(b)); TestBufferSize1(b); TestBufferSize2(b); system("pause");}
27.分别写出bool、int、float、指针类型的变量a与零比较的语句:
(1)bool: If(!a) or if(a) (2)int If(a == 0) (4)float: Const EXPRESSION EXP = 0.000001 If(a < EXP && a >-EXP) (5)指针: If(a != NULL) or if(a == NULL) 28.如何判断一段程序是由c编译还是由c++编译程序编译的?#ifdef __cplusplus cout << "c++";#else printf("c");#endif
29.讨论含参数的宏与函数优缺点: | 带参宏 | 函数 | 处理时间 | 编译时 | 程序运行时 | 参数类型 | 没有参数类型问题 | 定义实参,形参类型 | 处理过程 | 不分配内存 | 分配内存 | 程序长度 | 变长 | 不变 | 运行速度 | 不占用运行时间 | 调用和返回占用运行时间 | 30.使用两个栈来实现一个队列的功能设两个栈A,B,并将其初始化为空 入队: 将新元素push入栈A; 出队: (1) 判断栈B是否为空 (2) 若不为空,则将栈A中的所有元素依次pop出,并push到栈B (3) 将栈B的栈顶元素pop出 这样的实现,入队与出队的平摊复杂度都为O(1) 31.位操作给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a的bit 3。在以上操作中,保持其它位不变。 (1) 通过宏定义,以及bit masks操作。这种方法可移植性高 #define BIT3 (0x01 << 3)static int a;void SetBit3(void) { a |= BIT3;}void ClearBit3(void) { a &= ~BIT3;}
(2) 使用bit fields。Bit fields是被扔到C语言死角的方式。这种方法保证了你的代码在不同编译器之间是不可移植的,同时也保证代码是不可重用的。 32.访问固定的内存位置嵌入式编程中,经常会去访问某个特定内存位置的数据。在某个工程中,需要设置一个绝对地址为0x56a3的整型变量的值为0x3344。编译器是一个纯粹的ANSI编译器。 (1) 方法一: int *ptr = NULL;ptr = (int *)0x56a3;*ptr = 0x3344;
(2) 方法二: *(int * const)(0x56a3) = 0x3344;
推荐使用第一种。 33.中断中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供了一种扩展—让标准C支持中断。 具体代表的事实是,产生了一个新的关键字__interrupt。 下面这段代码使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评价这段代码。 __interrupt double compute_area(double radius) { double area = PI * radius * radius; printf("\nArea = %f", area); return area;}
(1) ISR不能返回一个值 (2) ISR不能传递参数 (3) 在许多编译器中,浮点数一般是不可重入的。并且,ISR应该是短而有效的,在ISR中做浮点数运算是不明智的。 (4) Printf()经常有重入与性能上的问题 34.typedefTypedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事情。 例: #define dPS struct s *Typedef struct s *tPS;
以上两种情况都是要定义dPS和tPS作为一个指向结构s的指针。哪种方法更好? 答:typedef更好,原因请看下面示例。 dPS s1,s2;tPS s3,s4;
第一个展开为struct s *s1,s2;此时,s1指向的是结构的指针,而s2是一个实际的结构。 35.写一个“标准”的宏(1)交换两个参数的宏定义 #define SWAP(a, b)\ (a) = (a) + (b);\ (b) = (a) - (b);\ (a) = (a) - (b);\
(2)输入两个参数,输出较小的参数 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
(3)1年中有多少秒?(忽略闰年) #define SECONDS_OF_YEAR (60 * 60 * 24 * 365)UL
(4)已知一个数组table,使用宏定义求出数组元素的个数 #define TABLE_SIZE (sizeof(table) / siezof(table[0]))
|