目录
- 1.中断处理优先级
- 2. 头文件中的#ifndef / #define / #endif的作用是什么?(5分)
- 3. Extern “C”在什么时候需要使用?(5分)
- 4. DWORD dwDevice[]={1,2,…..,x,n..};
- 5. 写一个标准宏MIN
- 6. Char * p=”1234”; char p1[]=”1234”;
- sizeof 和strlen的区别
- 7. typedef union {long i; int k[5]; char c;} DATE;
- 8、new和malloc的用法及区别
- retuan返回值注意事项
- 内存分区 全局区 堆区 栈区 代码区
- 9. static全局变量与普通的全局变量有什么区别?
- 10.用预处理指令#define 声明一个常数,用以表明1 年中有多少秒?
- 11. 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
- 11. 预处理器标识#error 的目的是什么?
- 12. 嵌入式系统中经常要用到无限循环,你怎么样用C 编写死循环呢?
- 13.用变量a 给出下面的定义: int a;int *a;
- 14 关键字static 的作用是什么?
- 15 简单回答关键字const 有什么含意?
- 16 关键字volatile 有什么含意? 并给出三个不同的例子。
- 缓存 CPU
- 四、编程题(30分)
- 1. 下面的代码中,调用Test函数后得到什么结果
- 2. 写两个函数,一个设置变量a的BIT3,另一个清除变量a的BIT3,
- 3. 写一个宏定义,swap(x,y)交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;(10分)
- 4. 实现一个环行BUFFER,包含初始化,读,写函数。(10分)
- 在1G内存的计算机中能否malloc(1.2G)?
- 指针与引用的相同和区别;如何相互转换?
- 为什么函数参数常常设置为引用
- C语言检索内存情况 内存分配的方式
- 函数头文件的声明前加extern 与不加extern 有什么区别
- 函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解...
- 重写memcpy()函数需要注意哪些问题
- GPIO 口一般有哪三个寄存器?
- GPIO的工作模式
- register关键字的作用
- const与#define的特点与区别
- #define和typedef的比较
- #define和枚举enum的区别
1.中断处理优先级
某计算机系统共有五级中断,其中断响应优先级从高到低为1–>2–>3–>4–>5。
但操作系统的中断处理部分作如下规定:
处理1级中断时屏蔽2、3、4和5级中断;
处理2级中断时屏蔽3、4、5级中断,处理4级中断时不屏蔽其它中断;
处理3级中断时屏蔽4 和5级中断;处理5级中断时屏蔽4级中断。
试问中断处理优先级(从高到低)是什么?(5分)
1 2 3 5 4
2. 头文件中的#ifndef / #define / #endif的作用是什么?(5分)
#ifndef #endif避免头文件重定义
在较大的工程中常常包含重复的头文件
这两条条件编译预处理指令中#ifndef会检查标识符是否已经被定义再决定执不执行后面的代码
#endif表示条件编译结束
#define 用来定义程序中经常使用的常量,当要改变这个常量的值的时候,就不需要对整个程序一个一个修改
而只需要修改定义处的值,比如GPIO端口位置
3. Extern “C”在什么时候需要使用?(5分)
C和C++的混合编程时
在C++源文件中的语句前面加上extern“C”,表明它按照类C的编译和连接规约来编译和链接,
这样在C的代码中就可以调用C++的函数或者变量,
例如函数void fun(int),c++编译器会编译成_int_fun() c编译器会编译成_fun(),查找时同样会以这种方式查找
extern "C" 就是提示C++的编译器使用C语言的方式进行编译或者链接
4. DWORD dwDevice[]={1,2,……,x,n…};
如有DWORD dwDevice[]={1,2,……,x,n…};
则Sizeof(dwDevice)/sizeof(dwDevice[0])是什么含义?(5分)
该语句用来计算dwDevice数组中的元素个数,含义为整个数组大小/单个数组元素的大小
5. 写一个标准宏MIN
写一个标准宏MIN,输入两个参数并返回较小的一个。
MIN(a++,b);这种用法有什么问题吗?
宏在展开的时候是简单的文本替换,可能会有多次++自增操作
6. Char * p=”1234”; char p1[]=”1234”;
Char * p=”1234”; char p1[]=”1234”;
则p与p1有不同吗?如有,有什么不同。
Sizeof(p)和sizeof(p1)得到的结果不同,p是指针大小8字节,p1包含结束符号”\0”所以为5
&p和&p1得到的数据类型不同&p得到的是“指向指针的指针类型”,&p1得到的是” 指向长度为5的数组的指针char (*)[5] 类型”
可修改性,由于 char* p 指向的是字符串常量,所以不能通过 p 来更改字符串的内容。
任何修改字符串的操作都是非法的。而 char p1[] 是一个字符数组,它的内容可以被修改。
sizeof 和strlen的区别
strlen是一个函数,它测量字符串除去’\0’以外的字符数;
sizeof是一个关键字,测量对象或者类型所占用的字节数,包括\0在内
特别的,对于sizeof关键字,当测量指针时,得到的是地址的长度,当sizeof测量数组名的时候,得到的是整个数组的长度,当函数调用数组做为参数的时候,用sizeof测量得到的仍然是一个指针的长度。
int main(int argc, char** argv) {
int a=1;
int b[] = {1};
int * p1 = &a;
cout <<sizeof(b)<<endl<<sizeof(p1);
return 0;
}
int 长4字节
int指针长8字节
7. typedef union {long i; int k[5]; char c;} DATE;
有如下语句:(5分)
typedef union {long i; int k[5]; char c;} DATE;
DATE max;
则sizeof(max)的值为多少?
Union表示联合体,由于联合的大小由最大成员决定
所以 sizeof(max) 的值是 sizeof(long) 的大小为 8 字节。
8、new和malloc的用法及区别
- malloc与free是C++、C语言的标准库函数,new/delete是C++的运算符,它们都可以用于申请动态内存和释放内存
- 对于非内部数据类型的对象而言,只用mallo/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放工作的运算符delete,注意new/delete不是库函数而是运算符。
- C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存
- new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。
realloc()函数用于调整之前分配的内存块的大小。它的原型是void* realloc(void*ptr,size_t size);,它将ptr指向的内存块的大小调整为size字节,并返回一个指向新内存块的指针。如果调整失败,realloc()会返回NULL,并保存原来的内存块不变
总的来说,malloc()和new都是用于动态分配内存的,但new更加类型安全并且具有自动初始化的特性,适用于C++中动态对象的分配i。realloc()用于调整之前分配的内存块的大小。
retuan返回值注意事项
不能返回局部变量指针或者数据名或者引用
(1)、malloc和free
函数声明:
void *malloc(int size);
使用
int* p;
p = (int*)malloc(sizeof(int));
free(p);//释放内存
new 和 malloc的区别.
- new 是操作符,而malloc是函数。
- new 在调用的时候先分配内存,再调用构造函数,释放的时候调用析构函数。
- new 是类型安全的, malloc返回 void*。
- new 可以被重载。
- new 分配内存更直接和安全。malloc可以被 realloc
- new 发生错误抛出异常,malloc 返回null。
- malloc 可以分配任意字节, new 只能分配实例所占内存的整数倍大小。
https://blog.csdn.net/qq_43530773/article/details/113895903
内存分区 全局区 堆区 栈区 代码区
-
代码区
存放函数的二进制代码即CPU执行的机器指令
通常代码区是可共享的(另外的执行程序可以调动它),目的是对于频繁执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,原因是防止程序意外修改它的指令,另外,代码区还规划了局部变量的相关信息。
总结:你所写的代码都会放入到代码区中,代码区的特点使共享和只读 -
全局区
存放全局变量和静态变量以及常量
全局区有多种叫法,全局区、静态区、数据区、全局静态区、静态全局区
全局区可以细分为
data区,存放初始化的全局变量、静态变量和常量。
bss区,存放未初始化的全局变量、静态变量,这些未初始化的数据在程序执行前会自动被系统初始化为0或NULL
常量区,存放常量,如const修饰的全局变量、字符串常量等 -
栈区:由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。
-
堆区:堆的容量远远大于栈,用于动态内存分配由程序员分配和释放,如果程序员不释放,程序结束时由操作系统回收
可以看到各个区域划分互不干扰,同一个区内的地址接近
[内存四区](https://zhuanlan.zhihu.com/p/120997610)
内存四区的意义:不同区域存放的数据赋予不同的生命周期,使变成更加灵活
9. static全局变量与普通的全局变量有什么区别?
1、局部变量(在函数内部定义的变量)
对于普通局部变量
- 编译器一般不对它初始化,所以它的初始值不确定
- 存储于进程栈空间,使用完毕后立即释放
对于用static修饰的局部变量 - 函数中一段用static初始化的代码不管调用多少次函数,static初始化都只执行一次
- 变量在全局数据区分配内存空间,即使函数返回,它的值也保持不变
#include <stdio.h>
void fn(void)
{
int n = 10;
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fn_static(void)
{
static int n = 10;
printf("static n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
int main(void)
{
fn();
printf("--------------------\n");
fn_static();
printf("--------------------\n");
fn();
printf("--------------------\n");
fn_static();
fn_static();
fn_static();
fn_static();
fn_static();
return 0;
}
普通全局变量
- 在全局数据区分配存储空间,编译器自动对其初始化
- 对整个工程可见,其他文件可以使用extern外部声明后直接使用
静态全局变量
- 静态全局变量仅对当前文件可见,其他文件不可访问
总结
在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
函数
对于static关键字,函数与全局变量的规则类似,在函数返回类型前加上static,就是静态函数
静态函数
- 静态函数只能在声明他的文件中可见,其他文件不能引用该函数
- 因此不同文件可以使用相同名字的静态函数,互不影响
总结
在定义不需要与其他文件共享的函数时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
对于C++的类中
静态数据成员
- 是类的所有对象的共享成员,它在内存中只占一份空间,改变它的值,对象中这个数据成员的值都被改变
- 在程序开始运行时被分配空间,在程序结束之后才释放
静态成员函数
- 非静态成员函数有this指针而静态成员函数没有this指针
- 静态成员函数余姚用来访问静态数据成员,而不能访问非静态成员
static全局变量与普通的全局变量有什么区别?
static局部变量和普通局部变量有什么区别?
static函数与普通函数有什么区别?
(1)static 局部变量与普通局部变量的区别
初始化:前者值只能被初始化一次,并且如果不手动初始化,会被自动初始化为0,后者初始化后可以被修改
(2)static 全局变量与普通全局变量的区别:
可见性:前者对其他源文件不可见,无法在其他源文件中用extern引用,后者则可以用extern引用
初始化:前者值只能被初始化一次,并且如果不手动初始化,会被自动初始化为0,后者初始化后可以被修改
(3)static 函数与普通函数的区别:
占用内存:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
可见性:前者对其他源文件不可见,无法在其他源文件中用extern引用,后者则可以用extern引用,但是也可以通过函数指针或者用普通函数调用static函数的方式间接调用static函数
10.用预处理指令#define 声明一个常数,用以表明1 年中有多少秒?
用预处理指令#define 声明一个常数,用以表明1 年中有多少秒?
#define SECONDS_IN_YEAR(365*24*60*60)
如果要判断闰年可以用下面的宏函数
#define SECONDS_IN_YEAR(year) ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 366 * 24 * 60 * 60 : 365 * 24 * 60 * 60)
11. 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(a, b) ((a) < (b) ? (a) : (b))
11. 预处理器标识#error 的目的是什么?
预处理器标识#error 的目的是什么?
#error 是一个预处理器指令,它的主要目的是在编译过程中检测然后生成错误消息,并停止编译。
#ifndef FUN_H
#error "ERROR 停止编译"
#endif
12. 嵌入式系统中经常要用到无限循环,你怎么样用C 编写死循环呢?
嵌入式系统中经常要用到无限循环,你怎么样用C 编写死循环呢?
两种方法
While(1)
For(;;)中间没条件就无限循环
While(1)比较简洁所以我常用,但是for可以节省一次执行表达式的操作,追求效率的时候应该用for
13.用变量a 给出下面的定义: int a;int *a;
用变量a 给出下面的定义:
a) 一个整型数
int a;
b)一个指向整型数的指针
int *a;
c)一个指向指针的的指针,它指向的指针是指向一个整型数
int **a;
d)一个有10 个整型数的数组
int a[10];
e) 一个有10 个指针的数组,该指针是指向一个整型数的。
int *a[10];
f) 一个指向有10 个整型数数组的指针
int (*a)[10]
g) 一个指向函数的指针, 该函数有一个整型参数并返回一个整型数
int(*a)(int);
h)一个有10 个指针的数组, 该指针指向一个函数, 该函数有一个整型参数并返回一个整型数
int (*a[10])(int);
14 关键字static 的作用是什么?
关键字static 的作用是什么?
这个问题很少有人能回答完全。在C 语言中,关键字static 有三个明显的作用:
1、修饰局部变量
用静态关键字static修饰的局部变量,在编译的过程中,会在数据区为该变量开辟空间,并对其进行初始化,如果代码中未对其进行初始化,则系统默认初始化为0。
2、修饰全局变量
普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
3、修饰函数
在函数的返回类型前加上static,就是静态函数静态函数只能在声明它的文件中可见,其他文件不能引用该函数
不同的文件可以使用相同名字的静态函数,互不影响
15 简单回答关键字const 有什么含意?
const关键字修饰后,会把这个变量变为一个只读变量,属性改为只读,站在实现者的角度,const可以防止函数对实参修改带来的问题。站在调用者的角度来说,它对于实参只能访问,不能修改。所以、合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
- C语言中const修饰的是只读变量,不能通过变量名来修改,只能通过其他方式修改(通过内存指针修改);
- C++修饰的是常量,存放在符号表中,不能修改;
- C++修饰成员函数是常成员函数,函数内部只能访问数据,不能修改数据;
- C++修饰成员变量是一个常量,需要通过构造函数后面的初始化列表来初始化。
简单回答关键字const 有什么含意?
下面的声明都是什么意思?
const int a;
int const a;
上面两种写法等价,都是常量整型
const int *a;
指向整型常量的指针
int * const a;
常量指针,指向整型
int const * a const;
这是种错误语法,修改一下应该写成 “const int * const a”是一个指向整型常量的常量指针(使用时记得要初始化)
(右向左看)
16 关键字volatile 有什么含意? 并给出三个不同的例子。
变量的访问
1、读变量
从内存中读取到寄存器(CPU中的寄存器)
2、写变量
从寄存器(CPU中的寄存器)写入内存
实例
int a,b;//位a,b申请内存
a = 1;//1->寄存器->内存(&a)
b = a;//内存(&b) ->寄存器->内存(&a)
优化
在上下两个赋值之间,寄存器的值没有变那么可以这么做,但是如果变了就会出问题
什么时候会出现这种情况?
当连续把a的值分别赋给b和c,内存到寄存器再到内存的这个过程就可能被优化为仅仅寄存器到内存,但是有以下的特殊情况
- 这个寄存器为硬件对应的寄存器,硬件寄存器的值有可能会随着硬件工作状态的变化改变(AD转换后的值存放的寄存器)
- 中断服务程序中修改其他程序中的变量,需要用volatile修饰
- 多线程应用中被几个任务共享的变量
b站视频详解volatile
代码优化
在计算机工作的时候,CPU处理速度比内存访问速度快很多,为了提升计算机整体性能,在软硬件层面都有相应的机制去优化内存的访问次数
硬件层面:
- 高速缓存(cache)
软件层面: - 编码优化(程序员)
- 编译优化(编译器)
volatile关键字直译意思是容易挥发,易变化的意思,它修饰的变量表示该变量的值很容易由于外部因素而发生改变,要求编译器在使用时必须小心的从内存中读取变量的值,而不是使用上一次从内存加载到寄存器中的值
volatile关键字的主要用途是为了防止编译器优化,告诉编译器,在使用它修饰的变量时,必须每次从内存中重新读值,而不是直接使用上一次从内存中加载到寄存器中的值。
缓存 CPU
缓存
定义:凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为Cache
被扩充概念:如今缓存的概念已被扩充,不仅在CPU和主内存之间有Cache,而且在内存和硬盘之间也有Cache(磁盘缓存),乃至在硬盘与网络之间也有某种意义上的Cache──称为Internet临时文件夹或网络内容缓存等。
存放的数据:内存中被CPU访问最频繁的数据和指令被复制入CPU中的缓存
作用:
- 用于协调两者数据传输速度差异的结构
实际例子
- 缓存是CPU的一部分
- 缓存时硬盘控制器上的一块内存芯片(硬盘上的缓存:当硬盘存取零碎数据时需要不断地在硬盘与内存之间交换数据)
CPU
二:CPU
(1)组成
一般由逻辑运算单元、控制单元和存储单元(寄存器)组成
(2)为什么需要缓存
事例:
1.因为缓存只是内存中少部分数据的复制品,所以CPU到缓存中寻找数据时,也会出现找不到的情况(因为这些数据没有从内存复制到缓存中去),这时CPU还是会到内存中去找数据,这样系统的速度就慢下来了,不过CPU会把这些数据复制到缓存中去,以便下一次不要再到内存中去取。 2.因为随着时间的变化,被访问得最频繁的数据不是一成不变的,也就是说,刚才还不频繁的数据,此时已经需要被频繁的访问,刚才还是最频繁的数据,现在又不频繁了,所以说缓存中的数据要经常按照一定的算法来更换,这样才能保证缓存中的数据是被访问最频繁的
(3)作用
缩短延迟
访问缓存的时间应该尽可能缩短,可以通过多种的方式缩短这个时间,比如能够通过减小缓存的大小或关联性来降低缓存的延迟,还有方式预测、增加带宽等方法。
提升命中率
所谓的命中率是在高速缓存中找到内存引用的速率,我们希望能够首先通过缓存中获得信息,以得到速度优势,所以缓存需要最大限度地实现这一目标。对于单个高速缓存,大小、关联性和块大小决定命中率。
降低更低级别内存下的开销
高速缓存是内存层次结构的一部分,其性能会影响其它性能,处理其它内存花费的时间越长,意味着系统性能越低,也就是说尽可能让处理在缓存中完成。
减少错失惩罚
缓存中不能命中是无法避免的事情,但是我们可以减少处理未命中所需的时间以获得更好的处理器性能,通过提升命中率并通过应用不同的优化,能够降低错失惩罚。
高速缓存是CPU中十分重要的部分,占据了大量的资源开销和成本,如果您看过CPU架构图的话,您就会发现缓存占据了至少50%的面积,绝对至关重要。
(4)CPU的多级缓存
一级缓存(L1 Cache)
CPU一级缓存,就是指CPU的第一层级的高速缓存,主要当担的工作是缓存指令和缓存数据。一级缓存的容量与结构对CPU性能影响十分大,但是由于它的结构比较复杂,又考虑到成本等因素,一般来说,CPU的一级缓存较小,通常CPU的一级缓存也就能做到256KB左右的水平。
二级缓存(L2 Cache66)
CPU二级缓存,就是指CPU的第二层级的高速缓存,而二级缓存的容量会直接影响到CPU的性能,二级缓存的容量越大越好。例如intel的第八代i7-8700处理器,共有六个核心数量,而每个核心都拥有256KB的二级缓存,属于各核心独享,这样二级缓存总数就达到了1.5MB。
三级缓存(L3 Cache)
CPU三级缓存,就是指CPU的第三层级的高速缓存,其作用是进一步降低内存的延迟,同时提升海量数据量计算时的性能。和一级缓存、二级缓存不同的是,三级缓存是核心共享的,能够将容量做的很大。
CPU的核心数量、高频高低都会影响性能,但如果让CPU更聪明、更有效率的执行计算任务,那么缓存的作用就至关重要了。
四、编程题(30分)
1. 下面的代码中,调用Test函数后得到什么结果
- 下面的代码中,调用Test函数后得到什么结果:
Char * GetMemory( )
{
Char p[]=“hello,this is a test”
Return p;
}
void Test(void)
{
char *str = NULL;
str=GetMemory( );
printf(str);
}
调用Test函数会返回不确定的结果,因为p指向一个局部变量,函数执行完后就被销毁了
2. 写两个函数,一个设置变量a的BIT3,另一个清除变量a的BIT3,
写两个函数,一个设置变量a的BIT3,另一个清除变量a的BIT3,在这个过程中要保持其它位的值不变。(5分)
假设a是int类型
void set_bit3(void)
{
a = a | (1<<3);
}
void clear_bit3(void)
{
a = a & ~(1<<3);
}
3. 写一个宏定义,swap(x,y)交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;(10分)
写一个宏定义,swap(x,y)交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;(10分)
#define swap(x,y) do{a^=b;b^=a;a^=b;}while(0)
4. 实现一个环行BUFFER,包含初始化,读,写函数。(10分)
思路:用线性数组模拟环形数组,难点在写入和读取时超出末尾,写入时首先检测是否超过最大容量,超过时提醒,再检测是否超过剩余容量,如果超过,通过分割数组为两段分别写入的方式,读取时也同理
验证,输入任意字符串,然后点击R读取出数组,在函数内设置的默认每次读取10个,读取后ValidRead会相应该改变位置没确保能完整的读出缓冲区
设置的默认逐个字符读取
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#define BUFF_MAX_LEN 16
#define VOS_OK 0
#define VOS_ERR -1
char *pHead = NULL; //环形缓冲区首地址
char *pValidRead = NULL; //已使用环形缓冲区首地址
char *pValidWrite = NULL; //已使用环形缓冲区尾地址
char *pTail = NULL; //环形缓冲区尾地址
int exitFlag=0;
//环形缓冲区初始化
void InitRingBuff()
{
if(NULL == pHead)
{
pHead = (char *)malloc(BUFF_MAX_LEN * sizeof(char));
}
memset(pHead, 0 , sizeof(BUFF_MAX_LEN));
pValidRead = pHead;
pValidWrite = pHead;//头尾指针初始都指向头部
pTail = pHead + BUFF_MAX_LEN; //移动到末尾
}
//环形缓冲区释放
void FreeRingBuff()
{
if(NULL != pHead)
{
free(pHead);
}
}
//向缓冲区写数据
int WriteRingBuff(char *pBuff, int AddLen)
{
if(NULL == pHead)
{
printf("缓冲区未初始化!\n");
return VOS_ERR;
}
if(AddLen > pTail - pHead)
{
printf("AddLen超过缓冲区最大长度\n");
return VOS_ERR;
}
//若新增的数据长度大于写指针和尾指针之间的长度
if(pValidWrite + AddLen > pTail)
{
int PreLen = pTail - pValidWrite;
int LastLen = AddLen - PreLen;
memcpy(pValidWrite, pBuff, PreLen);
memcpy(pHead, pBuff + PreLen, LastLen);
pValidWrite = pHead + LastLen; //新环形缓冲区尾地址
}
else
{
memcpy(pValidWrite, pBuff, AddLen); //将新数据内容添加到缓冲区
pValidWrite += AddLen; //新的有效数据尾地址
}
return VOS_OK;
}
//从缓冲区读数据
int ReadRingBuff(char *pBuff, int len)
{
if(NULL == pHead)
{
printf("缓冲区未初始化\n");
return VOS_ERR;
}
if(len > pTail - pHead)
{
printf("读取的数据长度超过缓冲区最大长度\n");
return VOS_ERR;
}
if(0 == len)
{
return VOS_OK;
}
if(pValidRead + len > pTail)
{
int PreLen = pTail - pValidRead;
int LastLen = len - PreLen;
memcpy(pBuff, pValidRead, PreLen);
memcpy(pBuff + PreLen, pHead, LastLen);
pValidRead = pHead + LastLen;
}
else
{
memcpy(pBuff, pValidRead, len);
pValidRead += len;
}
return len;
}
int main()
{
char c;
int len;
int readLen;
char readBuffer[10];
int i = 0;
InitRingBuff();
printf("请输入任意字符串,缓冲区长度默认为16\n");
while(!exitFlag)
{
c=getchar();
switch(c)
{
case 'Q':
exitFlag=1;
break;
case 'R':
readLen = ReadRingBuff(readBuffer,10);
printf("本次读取的缓冲区长度:%d\n",readLen);
if(readLen > 0)
{
for(i = 0;i < readLen;i++)
{
printf("%c ",(char)readBuffer[i]);
}
printf("\n");
}
break;
default :
if(c!='\n') WriteRingBuff((char*)&c,1);
break;
}
};
FreeRingBuff();
return 0;
}
在1G内存的计算机中能否malloc(1.2G)?
在 1G 内存的计算机中能否 malloc(1.2G) ?为什么?
malloc能够申请的空间大小与物理内存的大小没有直接关系,仅与程序的虚拟地址空间相关。
程序运行时,堆空间只是程序向操作系统申请划出来的一大块虚拟地址空间。
应用程序通过malloc申请空间,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
本题要申请空间的大小为 1.2G=2 30 × 1.2 Byte ,转换为十六进制约为 4CCC CCCC ,这个数值已经超过了 int 类型的表示范围,但还在 unsigned 的表示范围
这段代码可以用于测试电脑最多能分配多少内存
我的电脑是16G的实际内存,程序显示最多可以分配38G的虚拟内存空间
#include<stdio.h>
#include<stdlib.h>
int main(){
void *p=0;
int cnt=0;
//当p始终能够分配出空间,就进行计数
while ((p=malloc(100*1024*1024))){
cnt++;
}
//打印结果
printf("分配了%d00MB的空间\n",cnt);
//释放空间;
free(p);
return 0;
}
指针与引用的相同和区别;如何相互转换?
指针与引用的相同和区别;如何相互转换?
相同:
- 都是地址的概念,指针指向某一内存、它的内容是所指内存的地址;引用则是某块内存的别名。
- 从内存分配上看:两者都占内存,程序为指针会分配内存,一般是4个字节;而引用的本质是指针常量,指向对象不能变,但指向对象的值可以变。两者都是地址概念,所以本身都会占用内存。
区别: - 指针是实体,而引用是别名
- 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,而引用是对值的自增。
- 引用使用时无需解引用(*),指针需要解引用;(关于解引用大家可以看看这篇博客,传送门)
- 引用只能在定义时被初始化一次,之后不可变;指针可变;
- 引用不能为空,指针可以为空
- “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32位系统指针变量一般占用4字节内存。
- 引用没有const,指针有const;(指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)
#include "stdio.h"
int main(){
int x = 5;
int *p = &x;
int &q = x;
printf("%d %d\n",*p,sizeof(p));
printf("%d %d\n",q,sizeof(q));
}
-
指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。
-
引用转指针:把引用类型的对象用&取地址就获得指针了。
为什么函数参数常常设置为引用
- 避免拷贝:通过将参数声明为引用,可以避免在函数调用时对参数进行拷贝。如果参数是一个较大的对象,进行拷贝可能会产生较大的开销。通过使用引用,可以直接操作原始对象,而无需进行拷贝。
- 修改传入的对象:通过将参数声明为非常量引用,函数可以修改传入的对象的值。这允许函数对传入的对象进行修改,而不需要返回结果。这在一些需要修改对象状态的函数中很有用。
- 传递可变数量的参数:通过使用引用,函数可以接受可变数量的参数,而不需要提前确定参数的数量。这在实现类似于可变参数函数的功能时很有用,例如C++中的模板函数。
- 避免空指针检查:通过使用引用,可以避免在函数内部进行空指针检查。引用在声明时必须初始化,并且必须引用有效的对象,因此可以避免在函数内部进行空指针检查和处理。
C语言检索内存情况 内存分配的方式
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static变量
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用 ma c 或 new 申清任意多少的内存,程序员自己负责在何时用 free 或 delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
程序的内存空间
- 栈区(stack):由编译器自动分配释放,存放为运行函数而分配的局部变量,函数参数,返回数据、返回地址
- 堆区(heap):由程序员分配释放,若程序员不是放,程序结束时可能由OS回收。分配方式类似于链表
- 全局区(静态区)(static):存放全局变量、静态数据、常量。程序结束后由系统释放。
- 文字常量区:常量字符串放在这里。程序结束后由系统释放。
- 程序代码区:存放函数体(类成员函数和全局函数)的二进制代码。
函数头文件的声明前加extern 与不加extern 有什么区别
- 声明外部变量:在一个文件中使用 extern 关键字来声明一个在另一个文件中定义的全局变量。这样,编译器会知道该变量存在于其他文件中,从而避免重复定义。
- 声明外部函数:类似地,使用 extern 关键字可以声明一个在其他文件中定义的函数,这样编译器就能知道该函数的存在。
- 解决多文件之间的重复定义问题:当多个文件中存在同名的全局变量或函数时,使用 extern 可以避免重复定义的错误(static也可以,但是是将全局变量或全局函数限定在本文件中,会覆盖掉其他文件中的同名函数/变量)。
函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解…
__stdcall和__cdecl是两种调用约定(Calling Convention),用于指定函数在编译和链接过程中如何通过栈来传递参数和返回值。
__stdcall:
__stdcall 规定函数参数从右向左依次压入栈中,由被调用的函数自己负责清理栈空间,通常是通过ret指令来完成。。
__cdecl:
__cdecl 规定函数参数从右向左依次压入栈中,由调用方负责清理栈空间。由于调用方负责清理栈空间,因此在函数调用后,调用方负责通过栈指针恢复栈的原始状态。
重写memcpy()函数需要注意哪些问题
void *memcpy(void *dst, CO nst VOid *src, size_t n);
//lf copying takes place between objects that overlap, the behavior is undefined.
只是意思是对于地址重叠的情况,该函数行为是未定义的。
GPIO 口一般有哪三个寄存器?
端口配置低寄存器GPIOx_CRL
端口配置高寄存器(GPIOx_CRH)
(stm32中每个端口需要配置四个寄存器,16个端口就需要64个寄存器,所以分高寄存器和低寄存器,两个32位的寄存器)
低16位对应16个引脚,高16位保留
端口输入数据寄存器(GPIOx_IDR)
端口输出数据寄存器(GPIOx_ODR)
端口位设置/ 清除寄存器(GPIOx_BSRR)
高十六位进行位清除,低16位进行位设置
上面的合并为32位的寄存器用于保证位设置和位清除的同步性
下面两个用于单独设置位清除或者位设置
都只用了低16位,高16位保留
端口位清除寄存器(GPIOx_BRR)
端口锁定防止意外更改
端口配置锁定寄存器(GPIOx_LCKR)
GPIO的工作模式
(1)输入模式:浮空输入、带上拉输入、带下拉输入、模拟输入。
(2)输出模式:开漏输出、推挽输出、开漏复用输出、推挽复用输出。
register关键字的作用
register是一个关键字,用于向编译器提示某个变量可能会频繁的使用,因此希望将其存储在CPU寄存器中,就不需要每次都从内存中取数据了,以提高程序的性能,在现代编译器中,rigister不太常用,因为现代编译器会根据上下文和优化策略自行决定变量的存储位置,因此rigister关键字已经不太常用,在C99标准中已经被弱化,C++17也已不推荐使用。
一般我们会把经常使用或者频繁访问的局部变量用rigister修饰,注意,rigister修饰的局部变量不可以对其取地址,比如rigister int num = 5,不能&num。因为&是从内存中区。而num在CPU的寄存器中。
reigister不可以修饰全局变量和函数,因为生命周期和占用空间的问题。
大多数CPU的寄存器数量有限,因此编译器需要在局部变量之间共享这些寄存器。全局变量由于其生命周期较长,占用寄存器将减少可用于其他局部变量的寄存器数量。
const与#define的特点与区别
- 起作用的阶段:
define宏是在预处理阶段展开;const常量是在编译运行阶段使用。 - 起作用的方式:
#define知识简单的字符串替换,没有类型检查。而const有对应的数据类型,要进行类型判断,可以避免一些低级的错误。 - 存储方式:
#define知识进行替换展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;而const定义的只读变量在程序运行过程中只有一份备份。 - 代码调试:
const常量可以进行调试,define布恩那个调试,在与编译阶段就已经替换掉了
总结:const的优点
const常量有数据类型,而宏常量没有数据类型,没有安全检查在字符替换时可能会产生意料不到的错误
const可以调试
const可以节省内存空间,避免不必要的内存分配,提高效率
#define和typedef的比较
- 原理不同
#define是C语言中定义的语法,是预处理命令,在预处理时及逆行简单而机械的字符串替换,不作正确性检查,只有在编译已经被展开的源程序时才会发现可能的错误并报错
typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名。
typedef (int*) pINT;
#define pINT2 int*
效果相同?实则不同!实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b;
- 功能不同
typedef用来定义类型的别名,起到类型易与记忆的功能,另一个功能是定义机器无关的类型,便于移植,使得代码能在不同平台上保持一致,比如下方代码,只需要修改typedef定义处的类型就可以改变代码中所有的myint类型
typedef uint_8 myint
typedef uint_16 myint
typedef uint_32 myint
#define 不仅可以为类型取别名,还可以定义常量、变量、编译开关等。
-
作用域
#define没有作用域限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。 -
修饰指针时理解不同
修饰指针类型时作用不同:typedef int * pint; const pint p; //p是一个指向常整型数的指针;#define int * Pint; const Pint P; //P是一个指向整型数的常指针;
因为#define是简单的替换,可以替换为const int * p,是一个指向常整型的指针
而typedef定义的pint是一个指向整形的指针,const作用于指针,是一个指向整形的指针,指针地址是常量不能改变。
#define和枚举enum的区别
- #define宏常量是在预编译阶段进行简单替换。美剧常量是在编译的时候确定值
- 枚举常量可以调试,宏常量无法调试
- 枚举可以一次定义大量相关的常量,而#defiene宏一次只能定义一个