目录
1.结构体内存对齐,使用32位编译和64位有什么不同?2.判断机器大小端
6.计数排序,找出10000个数里面从小到大的第3400个,每个数范围1-1000,不可以使用基于比较的排序方法
9.请说明usrt,iic,usb的异同点(串/并,速度,全/半双工,总线拓扑)
10.linux内核态和用户态的通信方式,并列出效率最高的一个?
本人最近刷了比较多的笔试题,所以想把它们整理一下。。。
1.结构体内存对齐,使用32位编译和64位有什么不同?2.判断机器大小端
struct cat{
int id;
char color;
unsigned short age;
char *name;
void (*Jump)(void);
}Garfield;
1.32位编译
sizeof(Garfied)=4+1+2+4+4=16
2.64位编译
sizeof(Garfied)=4+1+2+8+8=24
64位编译的情况下,是8字节对齐
采用32位编译的情况下,是4字节对齐
这里有个小小的补充,那就是如果该结构体中有数组作为成员,那么上一个成员的内存大小就不用遵循当前变量是下一个变量整数倍的规则
如果当前成员中有#pragram pack(n)预编译指令,则所有成员必须以对齐n字节为标准,不用考虑当前机器类型(这个以前竟然不知道。。。后面做题碰到了)
2.如何判断机器大小端呢
机器大端指的是高地址存低位数据,低地址存高位数据
机器小端指的是高地址存高位数据,低地址存低位数据
所以这种题一般利用枚举体所有成员都是从低地址开始存放的特性(这里给出linux中的源码作为判断依据)3
static union{
char [4];
unsigned long mylong
}endian_test={{'1','?','?','long'}};
#define end_test (endian_test.mylong) //如果是1则说明数据从低地址开始存放
//是小端模式,
//如果是long则说明该数据是从大端存放。
3.描述xxx这个宏的作用
这里给出题目
#define offsetof(TYPE,MEMBER)(size_t)&(TYPE *)0->MEMBER)
#define XXX(ptr,type,member)({
const typeof(((type*)0->member)*_mptr=(ptr);
(type*)((char *)_mptr->offsetof(type,member));})
题目就是描述xxx这个宏的作用,其实学过内核链表的都知道,这一看就是内核链表中非常常用的两个宏,offsetof和container of 那么我们就一起来看看,
1.第一个宏offsetof很明显 ,就是从地址0开始,通过强制类型转换为type*类型的指针,然后指向结构体type中的成员member,前面加了个&取地址符,就是取得了member成员在结构体type中的偏移量,之后把数据类型转化为(size_t)无符号数,总而言之,就是求member的地址偏移量。
2.第二个宏就有点复杂了,我们来看看它的代码,首先创立了一个指针_mptr,然后把ptr的值赋给_mptr,这里是不是看的一头雾水。。。。别急,我们来看看这个_mptr是什么类型的,很显然,type*0->member是是结构体type的成员member类型,所以,这个ptr指针肯定是指向member的一个指针,然后赋值给了_mptr,(那么这里在下面为什么不直接用ptr呢),我觉得是为了简约,因为这里用了typeof ......,typedof有啥用就不说了.
再来看看第二行,这个就很明显了,用_mptr和offsetof相减,从而得到了结构体的首地址,那么答案也就出来了,这个宏的作用就是为了通过一个结构体的成员,和成员地址而得到这个结构体的首地址。这里用一张图来表示会更清楚。
4.简述C函数的传递
1.参数如何传递(_cdecl)
2.返回值如何传递
这个问题一开始是涉及到我的知识盲区了。
从网上找了一下
当一个函数被调用时,函数的参数会被传递给被调用的函数,同时函数的返回值会被返回给调用函数。函数的调用约定就是用来描述参数(返回值)是怎么传递并且由谁来平衡堆栈的。也就是说:函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。
常见的函数调用约定有:__stdcall,__cdecl(默认),__fastcall,__thiscall,__pascal等等。
它们按参数的传递顺序对这些约定可划分为:
从右到左依次入栈:__stdcall,__cdecl,__thiscall;
从左到右依次入栈:__pascal,__fastcall。
__cdecl:是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容 ;
__stdcall:是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容
记住就好
原文链接:https://blog.youkuaiyun.com/qq_38410730/article/details/80895986
那么返回值是如何传递的呢
一般来说,函数返回值都是通过eax来传递的,但eax每次只能存储4个字节的大小,那么大于4个字节的时候该怎么办呢
M <= 4字节,将返回值存储在eax返回;
4 < M <=8,把eax,edx联合起来。其中,edx存储高位,eax存储低位;
M>8
如果返回值的类型的尺寸太大,c语言在函数的返回时会使用一个临时的栈上内存作为中转,结果返回值对象会被拷贝两次。整个过程使用的是指向返回值的指针来进行拷贝的,而指针本身是通过eax返回的。因而不到万不得已,不要轻易返回大尺寸对象。
5.对寄存器的位操作
将REGN的指定位反转,地址为0x1F000010
void bit_resever(unsigned int nbit)
{
(volitile unsigned int)*ptr=(volitile unsigned int*)0x1F000010;
*ptr^=(0x01<<nbit)//反转指定位,保留其他位不变
}
特定位清零用&,特定位置一用|,特定位反转用^,所有位取反用~.
6.计数排序,找出10000个数里面从小到大的第3400个,每个数范围1-1000,不可以使用基于比较的排序方法
这道题其实就是用了计数排序,以下附上本人写的代码,计数排序是不用比较完成的排列方式,
核心在于将输入的数值放在额外开辟的数组中,时间复杂度为O(n);
#include <stdlib.h>
#include <stdio.h>
int main()
{
int array[10000];
int countarray[1000];
/*先生成10000个随机数,范围1-1000
并将其填充到统计数组中
*/
for(int i=0;i<=10000;i++)
{
array[i]=(rand()%1000+1);
countarray[array[i]]++;
}
int sum=0;
/*
统计数组变形,后面的数等于前面的数之和
*/
for(int i=0;i<=1000;i++)
{
sum+=countarray[i];
countarray[i]=sum;
}
for(int i=0;i<=1000;i++)
{
if(countarray[i]>=3400)
{
printf("%d\n",i);
printf("%d\n",countarray[i]);//打印第3400个数
break;
}
}
return 0;
}
7.简述嵌入式中断处理的过程
以下凭本人所学解答
关于中断的几个概念
中断向量:中断服务程序的入口地址
中断向量表:把系统所有的中断类型及其对应的中断向量按一定的规律存放在一个区域
中断源分为软中断,硬中断 ,内部中断和外部中断
中断嵌套:有更高优先级的中断程序提出中断请求时,这时会终止当前执行的级别较低的中断去处理级别更高的中断源,待处理完毕,再返回被中断了的服务程序继续执行。
保护现场:将断点处各个寄存器的内容,主要是当前的Ip和cs压入栈内,由用户实现。
请求中断->中断响应->保护现场->中断服务->恢复现场->中断返回。
cs为代码段寄存器,ip为指令指针寄存器
8.简述MMU,cache,cpu如何协同工作
cpu发出va请求数据,TLB接收到该地址(TLB是一块高速缓存,它缓存最近查找过的va对应的页表项)页表项不仅保存着物理页面的基地址,还保留着权限和是否cache的标志如果不允许cache,那直接发出PA从物理内存中读取数据到CPU内核;
如果允许cache,则以VA为索引到cache中查找是否缓存了要读取的数据,如果cache中已经缓存了该数据(称为cache hit)则直接返回给CPU内核,如果cache中没有缓存该数据(称为cache miss),则发出PA从物理内存中读取数据并缓存到cache中,同时返回给CPU内核。
9.请说明usrt,iic,usb的异同点(串/并,速度,全/半双工,总线拓扑)
UART:通用异步串行口,速率不快,可全双工,结构上一般由波特率产生器、UART发送器、UART接收器组成,硬件上两线,一收一发;
I2C:双向、两线、串行、多主控接口标准。速率不快,半双工,同步接口,具有总线仲裁机制,非常适合器件间近距离经常性数据通信,可实现设备组网;
SPI:高速同步串行口,高速,可全双工,收发独立,同步接口,可实现多个SPI设备互联,硬件3~4线;
USB 通用串行总线,高速,半双工,由主机、hub、设备组成。设备可以与下级hub相连构成星型结构
补充:
I2C通信原理
i2c的时序图,先发送从机设备地址,再发送寄存器地址,最后发送数据(主机写模式),主机设备每发送8位数据,从机必须发送一个应答信号ack,
总结,Sdata发出起始信号(将高电平拉低),在脉冲信号的作用下,scl每发过来一个脉冲,sda就会发送一个位,连续发8个位,之后在第九个时钟周期,会发出一个应答信号。总之,iic适合主从关系特别明显的设备进行通信,例如6818和htu21。
SPI通信原理
时序图 cpol为时钟极性 cpoh为时钟相位
时钟极性决定空闲时刻是高电平还是低电平 时钟相位则决定是上升沿采样数据还是下降沿采样数据。
显然,在空闲状态下,sck为低电平,当ss拉低时,传输开始,在模式0的情况下,数据的采样位于sck的上升沿,数据切换位于下降沿。
10.linux内核态和用户态的通信方式,并列出效率最高的一个?
netlink
全双工:procfs是基于文件系统,用于内核向用户发送消息;syscall是用户访问内核。它们都是单工通信方式。netlink是一种特殊的方式,用于内核和用户传递信息。
易于添加:为新特性添加system call、或者procfs是一件复杂的工作,它们会污染kernel(内核),破坏系统的稳定性,这是非常危险的。Netlink的添加,对内核的影响仅在于向netlink.h中添加一个固定的协议类型,然后内核模块和应用层的通信使用一套标准的API。
s