嵌入式C、汇编整理

嵌入式软件工程师经典笔试题

1、嵌入式c编程基础知识

程序编译时各部分介绍:

动态存储区、静态存储区、堆和栈的区别

空指针和void *类型指针

联合体判断大小端 

关于static:

修饰函数:使函数只能被其所在的文件访问,其他c文件不能访问static修饰的函数;

修饰变量

  • static修饰的变量会被保存在静态存储区而不是栈中(程序运行在栈中,运行完会被释放);
  • 但static修饰的变量虽然不会被释放,但跟全局 变量不同,static修饰的变量只能在其被定义的作用域内被访问;

关于define:

  • 避免了意义模糊的数字出现,使得程序语义流畅清晰
  • 便于修改函数
  • 提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。
define 的括号里的参数表示变量

关于const:

  • const修饰的常量具有不可变性
  • 编译器不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
常量指针和指针常量:
	char *str = "Hello World";
	//定义常量指针,即指针指向的对象是常量
	const char *pstr = str;
	printf("%c\n", *pstr);
	//*pstr = 'S';//error,指针指向的对象不可变
	printf("%c\n", *++pstr);//right,指针本身可变
	//定义指针常量 ,指针是 常量
	char* const ppstr = str;
	//ppstr++;//error
	*ppstr = 'N';//right

关于inline:

参考:inline

简单概括,inline修饰的内联函数在编译器编译时,将原本程序中的函数 调用步骤直接替换成了函数本体,因此减少了原本调用步骤产生的额外开销;但是缺点就是导致了代码膨胀!

关于volatile:

volatile为什么要修饰中断里的变量

题目:

 1、在一台64位的机器上,使用32位编译,Garfield 变量占用多少内存空间?64位编译又是如何?

struct CAT_s{
int ld;//32
char Color;、、
unsigned short Age;
char *Name;
void(*Jump)(void);
}Garfield;
数据类型 32位编译器/字节 64位编译器/字节 备注
char     1 1 例如:0xff
short 2 2 例如:0xffff
int 4 4 例如:0xffff ffff
long 4 8  
float 4 4  
char* 4 8 实际指向的是一个地址,地址字节数由编译器决定
long long 4 8  
double 8 8  
long double 10/12 10/16 有效字节10位,但 为了对齐实际分配12/16字节

所以使用32位编译器时:

  • 1个int 4字节;
  • char、unsigned shore 共3字节,为了对齐,算4字节;
  • char* 4字节;void * 4字节,

合计16字节

所以使用64位编译器时(8字节/64位对齐):

  • 1个int 4字节,char、unsigned shore 共3字节,为了对齐,合计算8字节;
  • char* 8字节;
  • void * 8字节,

合计24字节

2. 描述下面XXX 这个宏的作用。(总分10分)
#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));})

第一条语句:

#define offsetof(TYPE,MEMBER)   ((size_t)&((TYPE*)0)->MEMBER)

         TYPE代表一个结构体,MEMBER代表结构体成员,因此这条宏定义强行将结构体起始地址定义为0,那么member就代表成员地址的偏移量;

步骤如下:

  • (TYPE*)0  //将0转型为TYPE类型,即TYPE类型首地址为0
  • &((TYPE*)0)->MEMBER//取TYPE类型下MEMBER成员的地址,因为首地址为 0,因此可以看做成员地址的偏移量
  • (size_t)&((TYPE*)0)->MEMBER)//将改地址转型为(size_t)类型

第二条语句:

  • typeof(((type*)0)->member)  //取type类型下成员member的类型,例如:member是char,那么就代表char
  • const typeof(((type*)0)->member)*__mptr=(ptr);//将ptr指向的地址赋值给_mptr,_mptr的类型是const typeof(typeof到底是char,还是其他由member的类型决定!)
  • (type*)(char*)__mptr – offsetof(type,member))//将mptr转化成结构体下成员member的指针,而offsetof()是member的地址偏移量,因此相减得到结构体的起始地址!

 3、在一个多任务嵌入式系统中,有一个CPU 可直接寻址的32位寄存器REGn ,地址为 0x1F000010,编写一个安全的函数,将寄存器REGn 的指定位反转(要求保持其他bit 的值不变)

void bit_reversal(uint32_t nbit)
{
      *((volatial unsigned int *)0x1F000010)^=0x01<<nbit;
}
((volatial unsigned int *)0x1F000010)为地址定义,例如(int *)0x1F111111,代表可寻址的地址。

4、 有10000个正整数,每个数的取值范围均在1到1000之间,变成找出从小到大排在第 3400(从0开始算起)的那个数,将此数的值返回,要求不使用排序实现。(总分10分)

输入参数 数组a,数组长度n ,要查找的第index个数;返回:0 -未找到   i-第index数的数值

int fun_find(int *a, int n, int index)
{
	int c = 0;
	int count[1001] = {0};
	for (int i = 0; i < n; i++)
	{
		count[a[i]]++;
	}
	for (int i = 0; i < 1001; i++)
	{
		c += count[i];
		if (c >= index)
			return i;
	}
	return 0;
}




2、编程

2.1、输入与输出

输出各种进制:

int m = 255;
printf("m的十六进制:%x\n", m);
printf("m的十进制:  %d\n", m);
printf("m的八进制:  %o\n", m);
m的十六进制:ff
m的十进制:  255
m的八进制:  377

如果输入包含空格,例如输入“how are you”,用gets代替scanf;

字符串操作:

头文件string.h


内存分配:

头文件 alloc.h


算法:


2.2、编程题

请写一个函数,将一个16进制字符串转换为数字

#include <stdio.h>
#include <string.h>
int hextoint(char c)
{
	if (c >= 'a'&&c <= 'z')
		return (int)(c - 'a' + 10);
	else if (c >= 'A'&&c <= 'Z')
		return (int)(c - 'A' + 10);
	else if (c >= '0'&&c <= '9')
		return (int)(c - '0' + 0);
	else return 0xffffffff;//错误
}
int stringtoint(char* a)
{
	int sum = 0;
	int n = strlen(a);
	for (int i = 0; i <n; i++)
	{
		int temp = hextoint(a[i]);
		sum = 16 * sum+temp;
	}
	return sum;
}
int main()
{
	char a[10000];
	while(scanf("%s", &a)!=EOF)
	{ 		
		getchar();
		printf("%d\n", stringtoint(a));
	}	
}




3、简述

3.1. 简述处理器中断处理的过程(中断向量、中断保护现场、中断嵌套、中断返回等)。

中断向量:中断服务程序的入口地址。作用是:请求中断
当某一中断源需要CPU为其进行中断服务时,就输出中断请求信号,使中断控制系统的中断请求触发器置位,向CPU请求中断。系统要求中断请求信号一直保持到CPU对其进行中断响应为止。
中断响应:
CPU对系统内部中断源提出的中断请求必须响应,而且自动取得中断服务子程序的入口地址(入口地址一般存储在中断向量地址上),执行中断 服务子程序。对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期。
保护现场:
主程序和中断服务子程序都要使用CPU内部寄存器等资源,为使中断处理程序不破坏主程序中寄存器的内容,应先将断点处各寄存器的内容压入堆栈保护起来,再进入的中断处理。现场保护是由用户使用PUSH指令来实现的。
中断服务:
中断服务是执行中断的主体部分,不同的中断请求,有各自不同的中断服务内容,需要根据中断源所要完成的功能,事先编写相应的中断服务子程序存入内存,等待中断请求响应后调用执行。
恢复现场
当中断处理完毕后,用户通过POP指令将保存在堆栈中的各个寄存器的内容弹出,即恢复主程序断点处寄存器的原值。
中断返回
在中断服务子程序的最后要安排一条中断返回指令IRET,执行该指令,系统自动将堆栈内保存的 IP/EIP和CS值弹出,从而恢复主程序断点处的地址值,同时还自动恢复标志寄存器FR或EFR的内容,使CPU转到被中断的程序中继续执行
中断嵌套
是指中断系统正在执行一个中断服务时,有另一个优先级更高的中断提出中断请求,这时会暂时终止当前正在执行的级别较低的中断源的服务程序,去处理级别更高的中断源,待处理完毕,再返回到被中断了的中断服务程序继续执行,这个过程就是中断嵌套。

2. 简述处理器在读内存的过程中,CPU 核、cache 、MMU 如何协同工作?画出CPU 核、 cache 、MMU(内存管理单元) 、内存之间的关系示意图加以说明(可以以你熟悉的处理器为例)。(总分10分)


3、请说明总线接口USRT 、I2C 、USB 的异同点(串/并、速度、全/半双工、总线拓扑等)


4、汇编

【C语言】编译链接的详细过程

程序编译,链接过程




猜你喜欢

转载自blog.csdn.net/weixin_37058227/article/details/80935982