1、线性结构与非线性结构
线性结构是一个有序数据元素的集合。常用的线性结构有:线性表,栈,队列,双队列,数组,串。 非线性结构,数学用语,其逻辑特征是一个结点元素可能有多个直接前趋和多个直接后继。常见的非线性结构有:二维数组,多维数组,广义表,树(二叉树等)。
2、ARM程序的组成
此处所说的“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的bin映像(image)文件,这一点清注意区别。 一个ARM程序包含3部分:RO,RW和ZI RO是程序中的指令和常量 RW是程序中的已初始化变量 ZI是程序中的未初始化的变量
ARM映像文件的组成 所谓ARM映像文件就是指烧录到ROM中的bin文件,也成为image文件。以下用Image文件来称呼它。 Image文件包含了RO和RW数据。之所以Image文件不包含ZI数据,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。 Q:为什么Image中必须包含RO和RW? A:因为RO中的指令和常量以及RW中初始化过的变量是不能像ZI那样“无中生有”的
ARM程序的执行过程 从以上两点可以知道,烧录到ROM中的image文件与实际运行时的ARM程序之间并不是完全一样的。因此就有必要了解ARM程序是如何从ROM中的image到达实际运行状态的。 实际上,RO中的指令至少应该有这样的功能: 1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。 2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。 在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。
3、int *p=malloc(100); 求 sizeof(p)
sizeof(p)= 4 sizeof§ 只能测定 指针大小,32位机上得4。 sizeof 不能测定动态分配的数组大小。 strlen 可用于测定动态分配的字符数组长度但不适合int
int* ss = (int *)malloc(100);
void* p = (void*)malloc(100*sizeof(int));
char a[100] = "Hello";
char* b = "Hello";
char c[] = "Hello";
char d[] = "H\n";
_msize(ss);
sizeof(ss);
strlen(b);
sizeof(b);
sizeof(c);
strlen(c);
sizeof(p);
sizeof(a);
sizeof(d);
double* (*a)[3][6];
sizeof(a);
sizeof(*a);
sizeof(**a);
sizeof(***a);
sizeof(****a);
1.sizeof是运算符,不是函数 2.sizeof不能求得void类型的长度 不能用sizeof(void),这将导致编译错误:illegal sizeof operand。void是“空类型”,编译器无法确定void类型的变量的存储大小。 3.sizeof能求得void类型的指针的长度 编译器可以确定void类型的指针所占用的存储空间,sizeof(void*),编译器把指针的大小看做4byte。 4.sizeof能求得静态分配内存的数组的长度 int a[10];int n = sizeof(a);假设sizeof(int)等于4,则n= 10*4=40;特别要注意:char ch[]=”abc”;sizeof(ch);结果为4,注意字符串数组末尾有’\0’!通常我们可以利用sizeof来计算数组中包含的元素个数,其做法是:int n = sizeof(a)/sizeof(a[0]); 非常需要注意的是对函数的形参数组使用sizeof的情况。
void fun(int array[10])
{
int n = sizeof(array);
}
管形参是int的型数组,还是float型数组,或者其他任何用户自定义类型的数组,也不管数组包含多少个元素,这里的n都是4,在函数参数传递时,数组被转化成l指针。 5.sizeof不能求得动态分配的内存的大小 假如有如下语句:int* a = new int[10];int n = sizeof(a);n等于4,因为a是指针 6.sizeof不能对不完整的数组求长度
extern arrayA[];
sizeof(arrayA);
sizeof(arrayA)试图求不完整数组的大小。这里的不完整的数组是指数组大小没有确定的数组!sizeof运算符的功能就是求某种对象的大小,然而声明:extern int arrayA[]只是告诉编译器arrayA是一个整型数组,但是并没告诉编译器它包含多少个元素。 7.当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!
char ch = 1;
int num=1;
int n1 = sizeof(ch+num);
int n2 = sizeof(ch = ch+num);
表达式ch+num的计算结果的类型是int,因此n1的值为4!而表达式ch=ch+num;的结果的类型是char,记住虽然在计算ch+num时,结果为int,但是当把结果赋值给ch时又进行了类型转换,因此表达式的最终类型还是char,所以n2等于1。 n1,n2的值分别为4和1,其原因正是因为sizeof返回的是表达式计算结果的类型大小,而不是表达式中占用最大内存的变量的类型大小。 8.sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体!
int fun(int& num,const int& inc)
{
float div = 2.0;
double ret =0;
num = num+inc;
ret = num/div;
return ret;
}
int main()
{
int a = 3;
int b = 5;
sizeof(fun(a,b));
prinf("a=%d\n", a);
}
sizeof(fun(a,b))的值:其正确是4,因为用sizeof求函数调用的大小时,它得到的是函数返回类型的大小,而fun(a,b)的返回类型是int,sizeof(int)等于4.很多人把函数的返回类型和返回值的类型弄混淆了,认为sizeof(fun(a,b))的值是8,因为函数返回值是ret,而ret被定义成double,sizeof(doube)等于8。注意,虽然函数返回值类型是double,但是在函数返回时,将该值进行了类型转换(这里的转换不安全)。也有人错误的认为sizeof(fun(a,b))的值是12,它们的理由是:fun内部定义了两个局部变量,一个是float一个是double,而sizeof(float)+sizeof(doube)= 4+8=12。这样的答案看似很合理,其实他们是错误地认为这里的sizeof是在求函数内部的变量的大小了。 a的值:其正确答案是3!还记得特性6吗?这里很类似,sizeof的操作对象是函数调用时,它不执行函数体!为此,建议大家不要把函数体放在sizeof后面的括号里,这样容易让人误以为函数执行了,其实它根本没执行。 9.sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
struct A{
int num1;
int num2;
double num3;
};
struct B{
int num1;
double num3;
int num2;
};
由于存储变量地址对齐的问题,结构体大小计算必须满足两条原则: 一、结构体成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍) 二、结构体大小必须是所有成员大小的整数倍(不考虑数组和结构体) 注意:对齐方式很浪费空间,但是按照计算机的访问规则,对齐方式提升了效率。 参考链接https://blog.csdn.net/kangxiansen/article/details/117992458?spm=1001.2014.3001.5501 10.sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小! 位域:类型的大小都是以字节(byte)为基本单位的,比如sizeof(char)为1byte,sizeof(int)为4byte等。我们知道某个类型的大小确定了该类型所能定义的变量的范围,比如sizeof(char)为1byte,而1byte等于8bit,所以char类型的变量范围是-128——127,或者0——255(unsigned char),bool类型只取值true和false,按理所只用1bit(即1/8byte)就够了,但事实上sizeof(bool)等于1。因此我们可以认为bool变量浪费了87.5%的存储空间!这在某些存储空间有限的设备(比如嵌入式设备)上是不合适的,为此需要提供一种能对变量的存储空间精打细算的机制,这就是位域。结构体的成员变量后面跟上的一个冒号+一个整数,就代表位域。
Struct A
{
Bool b:1;
char ch1:4;
char ch2:4;
}item
其中b,ch1,ch2都是位域成员,而i是普通成员。该结构体的试图让bool类型的变量b只占用1个bit,让ch1和ch2分别只占用4个bit,以此来达到对内存精打细算的功能。
4、分配内存函数
malloc: void malloc(size_t size); malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。 功能:分配长度为num_bytes字节的内存块 返回值:如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。 calloc void *calloc(unsigned n,unsigned size); 功 能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。 跟malloc的区别:calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。 realloc 返回值: 如果将分配的内存减少,realloc仅仅是改变索引的信息; 如果是将分配的内存扩大,则有以下情况: 1)直接拓展:如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。 2)重新分配:如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。 3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。 注意: ①对于扩大内存的内存分配成功的两种情况,无论返回原指针还是新指针指向地址,原来指针均已自动释放过,不需要使用free再次释放掉原来的指针; ②申请的内存空间不会进行初始化; ③使用完毕以后必须手动释放内存空间,否则会造成内存泄露 new 与 delete new 与delete是C++预定的操作符,它们一般需要配套使用。 new用于从堆内存申请一块空间,一般动态用于动态申请内存空间,即根据程序需要,申请一定长度的空间。 自动计算需要分配的空间,在分配类类型的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。动态分配内置类型是否自动初始化取决于变量定义的位置,在函数体外定义的变量都初始化为0,在函数体内定义的内置类型变量都不进行初始化。
new 有以下的三种格式申请内存空间 new 数据类型int * p1=new int; new 数据类型(初始值)int *p2=new int(2); //*p2初始化值是2 new 数据类型[常量表达式]int *p3=new int[1000] //申请1000个单位内存空间 区别 (1)malloc 与 calloc malloc函数在申请内存时不会进行初始化,为随机值; calloc函数可以在申请内存后会自动初始化内存空间为0 (2)malloc 与 realloc 更改由malloc()函数分配的内存空间的大小,重新内存分配 (3)malloc 与 new malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针;不可自动初始化;
5、ARM异常类型
复位异常(Reset) 数据异常(Data Abort) 快速中断异常(FIQ) 外部中断异常(IRQ) 预取异常(Prefetch Abort) 软中断异常(SWI) 未定义异常(Undefined interrupt) 复位异常:当处理器复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行,复位异常中断通常用于系统上电和系统复位两种情况。 数据异常数据异常是由存储器发生数据中止信号,它由存储器访问指令 Load/Store 产生。 当数据访问指令的目标地址不存在,或者该地址不允许当前指令访问(权限不够)时,将产生数据访问中止异常。 快速中断异常当处理器的外部快速中断请求引脚有效,而且 CPSR (程序状态寄存器)的 F 控制位被清除时,处理器产生外部快速中断请求异常中断。 外部中断异常当处理器的外部中断请求引脚有效,而且 CPSR 的寄存器的 I 控制位被清除时,处理器产生外部中断请求异常中断。系统中各外设通过该异常中断请求处理服务。 预取异常预取异常是由系统存储器报告的。当处理器预取的指令的地址不存在,或者该地址不允许当前指令访问(权限不够)时,将产生预取异常。 如果系统中不包含 MMU,指令预取异常中断处理程序只是简单地报告错误并退出;若包含 MMU,引起异常的指令的物理地址被存储到内存中。 软中断异常 这是一个由用户定义的中断指令(SWI)。该异常由执行 SWI 指令产生,可用于用户模式下的程序调用特权操作指令。在实时操作系统中可以通过该机制实现系统功能调用。 未定义异常当 ARM 处理器执行协处理器指令时,它必须等待一个外部协处理器答应后,才能真正执行这条指令。若协处理器没有响应,则发生未定义指令异常。未定义指令异常可用于在没有物理协处理器的系统上,对协处理器进行软件仿真,或通过软件仿真实现指令集扩展。
6、排序算法
十种常见排序算法可以分为两大类: 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。 算法复杂度 必读参考链接https://blog.csdn.net/liang_gu/article/details/80627548
7、二分查找到时间复杂度
简单查找是从头开始,依次一个一个的对比。 二分查找如它名字描述那样:从中间开始找。 有一个有序数组:NSArray *array = @[@1, @3, @5, @7, @9, @10, @20]; 要查找3在这个数组中的index。 首先,找到数组中间那个元素:7,3比7小,并且3比7后面的所有元素都小,所以现在只需要在7之前的元素中查找,也就是:@[@1, @3, @5] 继续查找中间的那个元素,是3,OK,找到了。 二分查找,每查找一次都排除一半元素,直到最终剩下一个,这个就是我们要找的。 时间复杂度 时间复杂度由大O表示法描述(大O,大写字母O,不是数字0)。
运用简单查找算法,在n个元素的数组中查找一个数,情况最遭时,需要n步,所以简单查找的时间复杂度是O(n);
运用二分查找算法,在n个元素的数组中查找一个数,情况最遭时,需要(log2 n)步,所以二分查找的时间复杂度是O(log2 n)或者(log n)。
8、栈
栈(stack)又名堆栈,它是一种运算受限的线性表。特性:先进后出、后进先出,最先放入栈的最后拿出来,最后放入栈的最先拿出来限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 选择D,满足不了不允许连续三次进行退栈工作
允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
9、跳转地址
C语言使用函数指针跳转到程序固定地址(0x8000)执行程序的方法 使用函数指针,把一个纯数据强制转换为函数指针类型。
int main()
{
void ( *function)(void);
function =(void (*)())(0x8000);
my_function();
}
(void(*)())0x8000)();
10、c语言中的x*=y+8
- 和= 的优先级没有+ 高,所以先运算的是y+8,然后整体x=x*(y+8)。
11、以下选项中不属于c语言的类型是
A. signed short int B. unsigned long int C. singed int D.long short D A是有符号短整型数,B是无符号长整形数,C是有符号短整型(也就是A项)的缩写。
12、二叉树
一棵二叉树由根结点、左子树和右子树三部分组成,若规定 D、L、R 分别代表遍历根结点、遍历左子树、遍历右子树,则二叉树的遍历方式有 6 种:DLR、DRL、LDR、LRD、RDL、RLD。由于先遍历左子树和先遍历右子树在算法设计上没有本质区别,所以,只讨论三种方式:
DLR–前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 ) LDR–中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面) LRD–后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面) 层序遍历 层序遍历嘛,就是按层,从上到下,从左到右遍历
13、函数数据传递
一、形式参数和实际参数间的数据传递 形参出现在被调函数当中,在整个函数体内都可以使用。形参在定义时编译系统并不分配存储空间,只有在调用该函数时才分配内存单元。调用结束内存单元被释放,故形参只有在函数调用时有效,调用结束时不能再使用。 实参出现在主调函数当中,当函数调用时,主调函数把实参的值传送给被调函数的形参,从而实现函数间的数据传递。传递方式有两种:值传递和地址传递方式。 在调用函数过程中,系统会把实参的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数中的运算。在调用函数过程中发生的实参与形参间的数据传递。 当形参定义为变量时,实参可以是常量、变量和表达式,这种函数间的参数传递为值传递方式。值传递的特点是参数的“单向传递”;只能由实参传给形参,而不能由形参传给实参。
二、数组元素作为函数参数 数组元素又称为下标变量,它具有普通变量的一切性质,因此数组元素作为函数的实参进行数据传递是与普通变量没有任何区别,也是值传递。
三、地址传递方式 地址传递方式也是在形式参数和实际参数之间传递数据的一种方式。 地址传递方式所传递的是地址。调用函数时,将实际参数的地址赋予对应的形式参数作为其地址。由于形式参数和实际参数地址相同,即它们占用相同的内存。所以调用时,可以看成将实际参数的值传递给形式参数:返回时,可以看成将形式参数的值回带给对应的实际参数。其特点是“参数值的双向传递”。 由此可知,采用地址传递方式的实际参数只能是变量的地址、数组名(数组首地址)或指针变量等,而接受地址值的形式参数也只能是指针变量或数组名。
四、全局变量传递方式 全局变量是指在函数之外定义的变量。如果在函数之外定义了全局变量,则该变量的作用域从变量的定义位置开始到本源程序文件结束,在其作用域中任何函数均可以使用全局变量。在全局变量的作用域中,如某一函数改变了全局变量的值,则在其后使用的是改变后的全局变量的值。由于全局变量定义后,其作用域中的函数都可使用它,从而也可以实现函数间数据的传递。在这种传递方式中,利用的是系统分配给全局变量的内存单元,即全局变量的内存单元地址不变其实也是一种地址传递。
五、返回值方式 返回值方式不是在形式参数和实际参数之间传递数据,而是通过函数调用后直接返回一个值到主调函数中。因此这种方式通常适用于从被调函数中将一个值传回主调函数。 利用返回值的方式传递数据,在定义函数时,必须要注意下列两点: (1)函数头中要有“数据类型说明符”,说明该函数返回值的数据类型。 (2)函数体中应有“return(表达式);”语句,其中表达式值就是函数返回值。
14、内存四区
1 代码区 存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
总结:你所写的所有代码都会放入到代码区中,代码区的特点是共享和只读。 2 全局区 全局区中主要存放的数据有:全局变量、静态变量、常量(如字符串常量) 全局区的叫法有很多:全局区、静态区、数据区、全局静态区、静态全局区 这部分可以细分为data区和bss区 2.1 data区 data区里主要存放的是已经初始化的全局变量、静态变量和常量 2.2 bss区 bss区主要存放的是未初始化的全局变量、静态变量,这些未初始化的数据在程序执行前会自动被系统初始化为0或者NULL 2.3 常量区 常量区是全局区中划分的一个小区域,里面存放的是常量,如const修饰的全局变量、字符串常量、static静态局部变量等 总结:全局区存放的是全局变量、静态变量和常量 3 栈区(stack) 栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量、const修饰局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。 4 堆区(heap) 堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
15、内核子系统
Linux内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)5个子系统组成 进程调度与内存管理之间的关系:这两个子系统互相依赖。在多道程序环境下,程序要运行必须为之创建进程,而创建进程的第一件事情,就是将程序和数据装入内存。 进程间通信与内存管理的关系:进程间通信子系统要依赖内存管理支持共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。 虚拟文件系统与网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统(NFS),也利用内存管理支持RAMDISK设备。 内存管理与虚拟文件系统之间的关系:内存管理利用虚拟文件系统支持交换,交换进程(swapd)定期由调度程序调度,这也是内存管理依赖于进程调度的惟一原因。当一个进程存取的内存映射被换出时,内存管理向文件系统发出请求,同时,挂起当前正在运行的进程。 进程调度: 进程调度控制系统中的多个进程对CPU的访问,使得多个进程能在CPU中“微观串行,宏观并行”地执行。进程调度处于系统的中心位置,内核中其他的子系统都依赖它,因为每个子系统都需要挂起或恢复进程。 内存管理 内存管理的主要作用是控制多个进程安全地共享主内存区域。当CPU提供内存管理单元(MMU)时,Linux内存管理完成为每个进程进行虚拟内存到物理内存的转换。 虚拟文件系统 Linux虚拟文件系统(VFS)隐藏各种了硬件的具体细节,为所有的设备提供了统一的接口。而且,它独立于各个具体的文件系统,是对各种文件系统的一个抽象,它使用超级块super block存放文件系统相关信息,使用索引节点inode存放文件的物理信息,使用目录项dentry存放文件的逻辑信息。 网络接口 网络接口提供了对各种网络标准的存取和各种网络硬件的支持。如下图所示,在Linux中网络接口可分为网络协议和网络驱动程序,网络协议部分负责实现每一种可能的网络传输协议,网络设备驱动程序负责与硬件设备通信,每一种可能的硬件设备都有相应的设备驱动程序。 进程通信 进程通信支持提供进程之间的通信,Linux支持进程间的多种通信机制,包含信号量、共享内存、管道等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。
16、链表
必读参考链接https://blog.csdn.net/kangxiansen/article/details/118069692?spm=1001.2014.3001.5501
17、基本控制结构
顺序结构、选择结构、循环结构。 1、顺序结构:顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。 2、选择结构:选择程序结构用于判断给定的条件,根据判断的结果判断某些条件,根据判断的结果来控制程序的流程。使用选择结构语句时,要用条件表达式来描述条件。 3、循环结构 :循环结构可以减少源程序重复书写的工作量,用来描述重复执行某段算法的问题,这是程序设计中最能发挥计算机特长的程序结构 。循环结构可以看成是一个条件判断语句和一个向回转向语句的组合。另外,循环结构的三个要素:循环变量、循环体和循环终止条件. ,循环结构在程序框图中是利用判断框来表示,判断框内写上条件,两个出口分别对应着条件成立和条件不成立时所执行的不同指令,其中一个要指向循环体,然后再从循环体回到判断框的入口处
18、ptr++和(ptr++)的区别
++的优先级要高于*,所以两者运算的结果是一样的,也就是加括号和不加括号是效果是一样的。
int main()
{
char *ptr="hello";
printf("%c\n",*ptr);
printf("%c\n",*ptr++);
printf("%c\n",*(++ptr));
printf("%c\n",*(ptr+1));
}
屏幕输出的结果:h,h,l,l
ptr是一个指向字符型的指针,存放的是字符串“hello”中的第一个字符h的地址,并不是整个字符串。 我们来分析第一个输出ptr,意思直接取出ptr的值也就是h。 第二个输出ptr++,这个结合先运算地址ptr++,在就是取值运算。ptr++运算结果应该是先引用后地址加1,当前ptr指向的是字符串的第一个字符的位置,在运算取值运算先取出当前地址的值,也就是h,之后地址自增,指针指向下个字符e。 第三个输出(++ptr),和第二个类似先运算括号内的地址运算++ptr ,在从地址中取值。++ptr是地址先加1然后在参加运算,由于第二步的时候指针已经指向第二个字符e地址,所以经过运算++ptr之后指针指向下个字符地址,在取值运算得到 l。 第四个输出*(ptr+1)是输出当前ptr指向地址+1地址的值,也就是第二个 l。
19、实时性操作系统
实时操作系统(RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。 实时操作系统有硬实时和软实时之分,硬实时要求在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。 实时操作系统(RTOS)要求:
- 多任务
- 处理能被区分优先次序的进程线
- 一个中断水平的充份数量
20、extern、static、volatile和const用法
extern: 置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。 static 1.修饰局部变量 静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。 变量在全局数据区分配内存空间;编译器自动对其初始化,其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束。 2.修饰全局变量 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响,其他文件可以使用extern外部声明后直接使用 3.修饰函数 静态函数只能在声明它的文件中可见,其他文件不能引用该函数 不同的文件可以使用相同名字的静态函数,互不影响 volatile volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据。精确地说就是, 1.遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化 2.用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。 下面是使用volatile变量的几个场景: 1.中断服务程序中修改的供其它程序检测的变量需要加volatile; 2.多任务环境下各任务间共享的标志应该加volatile 3.存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。 const 1.用const修饰常量:定义时就初始化不能更改 2.用const修饰形参:func(const int a){} 该形参在函数里不能修改 3.用const修饰类成员函数:该函数对成员职能进行只读操作。
21、联合体Union
联合体成员完全就是共用一个内存首地址,并且各种变量名都可以同时使用 结构体和联合体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而联合体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙), 联合体占用的内存等于最长的成员占用的内存。
union U
{
char s[9];
int n;
double d;
};
s占9字节,n占4字节,d占8字节,因此其至少需9字节的空间。然而其实际大小并不是9,用运算符sizeof测试其大小为16.这是因为这里存在字节对齐的问题,9既不能被4整除,也不能被8整除。因此补充字节到16,这样就符合所有成员的自身对齐了。从这里可以看出联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,即其大小必须满足两个条件:1)大小足够容纳最宽的成员;2)大小能被其包含的所有基本数据类型的大小所整除。
22、堆栈溢出一般是由什么原因导致的?
答:1.函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。 2.动态申请空间使用之后没有释放。由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。 3.数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。 4.指针非法访问。指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。
23、嵌入式开发需要注意
1.MCU的选择 2.电源 3.普通I/O口 4.A/D电路与D/A电路 5.控制电路 6. 考虑低功耗 7. 考虑低成本 8. 内存资源 9. 存储资源 10.高效代码 11.屏幕资源
24、USB、Cashe、DDR、寄存器–速度比较
寄存器>Cashe>DDR>USB
寄存器: 由触发器(D触发器)组成,主要作用是用来暂时存放数码或指令。 寄存器是中央处理器内的组成部份。它跟CPU有关。 内存和寄存器是为了解决存储器读写速度而产生的多级存储机制。 寄存器亦称缓存,一般是指由基本触发器结构衍生出来的D触发,一般是一些与非门构成的结构,一般整合在CPU内,其读写速度更CPU的运行速度基本匹配。由于其性能优越,所以价格昂贵。一般好的CPU也就只有几MB的2级缓存,1级缓存更小。 不同的寄存器有不同的作用,如通用寄存器(GR)可以存放操作数、操作数的地址或中间结构;指令寄存器(IR)用以存放当前正在执行的指令。 寄存器应具有接收数据、存放数据和输出数据的功能,它由触发器和门电路组成。只有得到“存入脉冲”(又称“存入指令”、“写入指令”)时,寄存器才能接收数据;在得到“读出”指令时,寄存器才将数据输出。 寄存器存放数码的方式有并行和串行两种。并行方式是数码从各对应位输入端同时输入到寄存器中;串行方式是数码从一个输入端逐位输入到寄存器中。 寄存器读出数码的方式也有并行和串行两种。在并行方式中,被读出的数码同时出现在各位的输出端上;在串行方式中,被读出的数码在一个输出端逐位出现。 Cashe: Cache存储器,电脑中为高速缓冲存储器,是位于CPU和主存储器DRAM(Dynamic Random Access Memory)之间,规模较小,但速度很高的存储器,通常由SRAM(Static Random Access Memory 静态存储器)组成。它是位于CPU与内存间的一种容量较小但速度很高的存储器。CPU的速度远高于内存,当CPU直接从内存中存取数据时要等待一定时间周期,而Cache则可以保存CPU刚用过或循环使用的一部分数据,如果CPU需要再次使用该部分数据时可从Cache中直接调用,这样就避免了重复存取数据,减少了CPU的等待时间,因而提高了系统的效率。 DDR DDR=Double Data Rate双倍速率。DDR SDRAM=双倍速率同步动态随机存储器,DDR内存是在SDRAM内存基础上发展而来的,仍然沿用SDRAM生产体系。 双倍数据率同步动态随机存取存储器(英语:Double Data Rate Synchronous Dynamic Random Access Memory,简称DDR SDRAM)为具有双倍数据传输率的SDRAM,其数据传输速度为系统时钟频率的两倍,由于速度增加,其传输性能优于传统的SDRAM。 DDR SDRAM 在系统时钟的上升沿和下降沿都可以进行数据传输。 USB 是英文Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。
25、操作系统大端模式和小端模式
大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 (其实大端模式才是我们直观上认为的模式,和字符串存储的模式差类似) 小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为: 32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为: 在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
常见CPU的字节序 大端模式 : PowerPC、IBM、Sun 小端模式 : x86、DEC ARM既可以工作在大端模式,也可以工作在小端模式。
26、如何引用一个定义过的全局变化
可以用引用头文件的方式,也可以用extern关键字。
|