嵌入式C语言面试总结

简介

面试C语言嵌入式开发岗位,无论你是面试华为、美团、还是别的小公司,围绕的问题都差不多,只要做好准备,绝对心有成竹(请问:成竹是谁?)。

第一部分:C语言关键词的使用

static作用:
修饰函数和全局变量:限制作用域;修饰局部变量:数据放在静态数据区,类似于全局变量,但作用域仅为局部。
volatile作用:
volatile意为易变的,说明这个变量可能会意想不到的改变,优化器用到这个变量时,必须重新读取这个变量的值。应用场景:中断会访问到的变量、多线程共享的变量、硬件寄存器。
malloc 和 new的不同:
① 库不同:malloc/ free是 C++/C的标准库函数,而new/ delete是C++的运算符。
② 返回不同:malloc成功返回void*,否则返回NULL;new分配成功返回指定类型,失败会抛出bac_alloc异常。
③ 参数不同:malloc需要指定所需的内存大小,new自动分配大小。
strcpy和memcpy区别:
① 功能不同:Strcpy只能复制字符串、mencpy可以复制结构体、类等等;
② 使用不同:strcpy不需要指定长度,‘\0’自动结束。
③ 用途不同:只有在拷贝字符串的时候使用strcpy,其余则用memcpy。
switch的参数不能为浮点型。
#error:编译报错。
strlen与sizeof区别:
① strlen参数只能是字符串且以’\0’结束;sizeof参数可以是类型、函数;
② sizeof是运算符,在编译时计算好;strlen运行过后的结果。
例如:char str[]=”12”; strlen(str) =2;//strlen不含’\0’字符。
char a[10],strlen(a)=?//字符串长度不确定,因为没有初始化
#include:
#include “a.h” <a.h>;区别//<a.h>是从标准库开始搜索,”a.h”从工作路径开始搜素。

第二部分:C语言基础概念

局部变量能否和全局重名:能,局部会屏蔽全局,使用”::”可以引用全局变量。
程序的内存分配:
栈区:存放函数的参数值,局部变量值,系统自动分配和释放。在ucos线程创建任务时就需要指定栈区的大小 OS_STK。
堆区:动态分配的内存空间由malloc、new分配。
静态区:存放全局变量和静态局部变量。
常量区:存放常量字符串
代码区:存放二进制代码。
数组与指针的区别:
指针就是一个内存地址,可以指向任何地方,比如常量字符串,函数;数组储存多个相同类型的数据,有明确大小;
例如:
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; //此句话错误,因为p指向的是常量字符串,所以不能赋值。
(2) sizeof长度不同
char a[] = “hello world”;
char *p = a;
sizeof(a) = 12; // 12 字节,字符末尾存在一个“NULL”字符
sizeof§ = 4; // 4 字节,p是地址,地址的宽度=总线宽度。
混合运算规则:先转化成同一类型后运算、往长度增长的方向转化、所有float运算都以double进行的;char和short运算时,会转化成int型;赋值两边的类型不同时,右边数据类型转换成左边。
指针常量:char * const p;//p常量,地址不可修改,指向的值可改;可以这样记忆:指针(即地址)为常量,所以地址不能修改。
常量指针:const char *p;//指向p的值不可修改,即指向常量的指针。可以这样忆:常量类型的指针,即指向的内容为常量。
内存对齐:提高程序性能,比如int型,在对齐的条件下才能一次性访问到一个完整数据。
结构体内存大小:必须是最大内存变量的整数倍,
例如:
struct A{char a;int i;char b;};结构体占用空间
解析:最大内存是int,则内存必须是4的倍数;放了a之后,剩下3个字节放不下i,所以浪费3字节,然后放i和b;所以最终为1+3+4+1=9;字节必须最大的整数倍,所以是12。
Struct B{int I;char c;short s;};内存结构:iiiicxss;
排序法:冒泡、选择、插入、快速、堆、归并排序。
指针地址加1的理解
int a[5] = {1,2,3,4,5};
*(a+1) = 2;//首地址+1,则为a[1]
&a+1;//+1为加一个数组的大小。
位域
位域int a:16;//错误,位域长度不能超过8.
C语言编译过程为:预处理–>编译–>汇编–>链接
宏和函数的区别:宏运行更快,没有调用和返回时间,但是代码会变长。
大端和小端模式

分页和分段:页大小固定,系统决定一般4k;段根据功能决定大小。
数组与链表的区别:数组不能动态定义大小,所以使用之前定义足够大,会造成浪费。链表不会造成浪费;数组从栈中分配空间,链表从堆中申请。消息队列就是用的链表。
栈和队列的相同特点:只允许在端点处插入和删除。
线性数据结构:只有1个头和1个尾,节点与节点之间一一对应(二叉树尾非线性数据结构)
链表是否有环:快慢指针,如果fast(向前两步)和slow(向前一步)指针能够在链表中相遇,说明有环。环的入口,相遇点和起点各发出一个速度为一步的指针,两个指针相遇的地方就是环的入口.
判断是否为2的幂次方:思路:2的幂次方二进制一定是10000……,所以只需要判断二进制格式中1的个数为1。

第三部分:操作系统

实时系统RTOS特性:实时性和可靠性(要求能及时响应,并在规定时间完成)。
freeRTOS和ucos对比:
freeRTOS消耗资源少2~3k,ucos需要5k;
freeRTOS可设置优先级一样的任务;
freeRTOS商业免费,ucos收费;
freeRTOS不支持邮箱和标志事件组。
freeRTOS只支持TCP/IP,freeRTOS支持CAN、USB、FS;
ucos可靠性高、耐优化。
ucos功能:多线程、线程通信(信号量、互斥信号、标志事件组、邮箱、消息队列)、内存管理、时间管理
可剥夺型内核:ucos属于可剥夺型内核,中断返回执行高优先级任务。。
线程与进程的区别:线程是进程的一个执行单元;进程拥有资源的基本单元,线程不拥有系统资源;进程撤销时会增加系统的开销;
线程和进程使用的优缺点:线程开销小,但不利于资源管理和保护(进程之间一个挂了另一个不影响,所以有守护进程)
死锁:两个进程相互等待卡住(四大条件:互斥、请求与保持、非抢占、循环等待)。解决方法:不让死锁发生(别一直循环等待)或者解锁。
进程状态:就绪、运行、阻塞。

缓冲区溢出:危害程序崩溃,跳转到恶意代码;输入没检查导致。
临界资源:只能被一个进程所占用的资源。

第四部分:编程细节

清除变量A的bit3位:A &=~(0x01<<3);
对8位寄存器赋值:*( volatile uint8 *)addr = 0x12;//使用volatile修饰,详见volatile作用
交换两个参数值的宏定义:
方法一:#define SWAP(a,b) {a=a+b; b=a-b;a=a-b;}
方法二:#define SWAP(a,b) {a^=b; b^=a; a^=b;}
解释:b=a;:相当于b=b(ab)。而b(ab)等于abb。bb的结果为0,因为同一个数与本身相异或,结果必为0。因此b的值等于a^0,即a;
混合运算:uint8 a=6; int8 b=-6; 运算a+b时,b会转成uint8,即0xff-5,再运算。
a++运算:a=5,b=7;c=a+++b,即(a++)+b=12;
一句话实现x是否为2的若干次幂判断:((x&(x-1))?false:ture);
函数指针
int (*funcPtr)(int);//函数指针,指向int func(int param)的函数;
int (*funcPtr[5])(int);//函数指针数组。
BOOL、int、float、指针变量与“零值”比较的if语句
BOOL型变量:if(!var);//bool只有真和假,写成if(var == 0)不明确。
int型变量:if(var == 0);//很清晰的表明是和零值的比较。
float型变量:if((x>-0.00001)&&(x<0.00001));
指针型:if(var==NULL);//l良好编程习惯。

第五部分:数据结构与算法

数据结构:即数据的储存方式,常见数据结构数组、栈、队列、链表、树、图、哈希表。
定义链表

typedef struct {
uint16_t count;
struct LinkList * next;
}LinkList;
LinkList* list_head=NULL;

插入链表

uint8_t insert_Listnode(LinkList* list_node)
{
struct LinkList * currentNode =list_head;
while(currentNode)
{
if(currentNode ==list_node) return 1;
else
currentNode = currentNode ->next;
}
list->next= list_head;
list_head =list;
return 0;
}

删除链表

uint8_t detel_ListNode(struct LinkList* listNode)
{ struct LinkList * currentNode =list_head;
while(currentNode)
{
if(currentNode ->next==list_node)
{
currentNode ->next=list_node->next;
return 0;
}
else
target=target->next;
}
return 1;
}

冒泡法
时间复杂度O(n2)=0.5(n2)
思路:每次相邻两个数比较,若升序,则将大的数放到后面,一次循环过后,就会将最大的数放在最后.

void maopao(char *buf,uint32 length)
{
uint32 i,j;
for(i=0;i<length;i++)
{
for(j=0;j<length-i-1;j++)
{
if(buf[j]>buf[j+1])
{
buf[j] ^= buf[j+1];//每一次冒泡,进行一次交换。
buf[j+1] ^= buf[j];
buf[j] ^=buf[j+1];
}
}
}
}

选择排序
思路:从第一个数开始,每次和后面剩余的数进行比较,若升序,则如果后边的数比当前数字小,进行交换,和后面的所有的数比较、交换后,就会将当前的最小值放在当前的位置。

void xuanze(char *buf,uint32_t length)
{
uint32_t i,j;
for(i=0;i<length;i++)
{
for(j=i;j<length;j++)
{
if(buf[i]>buf[j])
{
buf[j] ^= buf[i];//找到最小的交换在前面。
buf[i] ^= buf[j];
buf[j] ^=buf[i];
}
}
}
}

插入法
我们用扑克的方法解释,首先我们抽到第一张牌,将它放在第一位,我们排序是从第二次抽牌开始,第二次抽起一张牌3,它比9小,所以将9向后移一位然后把3放在9原来的位置.再次抽牌2,发现它应该再3的前面,所以将3和9向后移,把2放到3原来的位置

void charu(char *buf,uint32_t length)
{
uint32_t i,j;
char data=0;
for(i=1;i<length;i++)
{
data = buf[i];//将牌拿在手中
for(j=i;j>0;j–)//和前面的所有牌比较
{
if(buf[j-1]>data)//如果前面的牌比手中的大,则往后移
{
buf[j] = buf[j-1];
}
else break;
}
buf[j] = data;//将"牌"插入
}
}

快速排序
步骤:选第一个元素为基准,把基准大的放右边,小的放左边。
对左边的数进行递归,对右边的进行递归。

void quickSort(int a[],int left,int right)
{
int i=left;
int j=right;
int temp=a[left];
if(left>=right)
return;
while(i!=j)
{
while(i<j&&a[j]>=temp)
j–;
if(j>i)
a[i]=a[j];
while(i<j&&a[i]<=temp)
i++;
if(i<j)
a[j]=a[i];
}
a[i]=temp;
quickSort(a,left,i-1);/递归左边/
quickSort(a,i+1,right);/递归右边/
}

二叉树
二叉树的定义:

typedef struct BiNode{
TElement data;
struct BiNode *lchild,*rchild;
}BiTNode, *BiTree;

遍历二叉树

void getTreeNodeNum(BiTree T,int &num){
if(!T){
num = 0;
}
num++;
if(T->lchild){
getTreeNodeNum(T->lchild,num);
}
if(T->rchild){
getTreeNodeNum(T->rchild,num);
}
}

二分查找

int32 BinarySearch(DataType* plist, uint32 len, DataType item)
{
uint32 icnt = 0;
int32 min = 0, mid = 0, max = len-1; //需定义为有符号类型
while(min <= max)
{
mid = (min + max)/2;
if(plist[mid] < item)
{
min = mid + 1;
}
else if(plist[mid] > item)
{
max = mid - 1;
}
else
{
return mid;
}
}
return -1;
}

字符串全排列

void func(char a[],int startPos)//startPos上图绿色部分开始的下标,比如0,代表从a[0]开始打印所有的全排列。
{
int i,temp;
if(startPos == strlen(a)){
printf("%s ",a);
}
for(i=startPos;i<strlen(a);i++)
{
temp = a[startPos];
a[startPos] = a[i];
a[i] = temp;
func(a,startPos+1);
temp = a[startPos];
a[startPos] = a[i];
a[i] = temp;
}
}

判断链表是否有环

bool IsExitsLoop(slist *head)
{
slist *slow = head, *fast = head;
while ( fast && fast->next )
{
slow = slow->next;
fast = fast->next->next;
if ( slow == fast) break;
}
return !(fast == NULL || fast->next == NULL);
}

发布了49 篇原创文章 · 获赞 76 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/fengweibo112/article/details/105092203