1、嵌入式c编程基础知识
程序编译时各部分介绍:
动态存储区、静态存储区、堆和栈的区别
空指针和void *类型指针
关于static:
修饰函数:使函数只能被其所在的文件访问,其他c文件不能访问static修饰的函数;
修饰变量:
- static修饰的变量会被保存在静态存储区而不是栈中(程序运行在栈中,运行完会被释放);
- 但static修饰的变量虽然不会被释放,但跟全局 变量不同,static修饰的变量只能在其被定义的作用域内被访问;
关于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:
题目:
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 的异同点(串/并、速度、全/半双工、总线拓扑等)