天天摸夜夜添添到高潮水汪汪

发布日期:2022-06-18 17:03    点击次数:136

镶嵌式 C 话语中三块难啃的硬骨头

 C话语在镶嵌式学习中是必备的学问,审核大部分操作都要围绕C话语进行,而其中有三块“难啃的硬骨头”真实是公认级别的。

01. 指针

指针公认最难融会的办法,亦然让许多初学者聘请撤消的径直原因

指针之是以难融会,因为指针本身等于一个变量,是一个相配特等的变量,专门存放地址的变量,这个地址需要给肯求空间智力装东西,而且因为是个变量不错中间赋值,这样一倒腾许多人就脱手犯晕了,绕不开弯了。C话语之是以被许多能手所心爱,等于指针的魔力,中间不错生动的切换,推论成果超高,这点亦然让小白晕菜的所在。

指针是学习绕不外去的学问点,而且学完C话语,下一步紧接着切换到数据结构和算法,指针是切换的重心,指针搞不定下一步进行起来就很难,会让许多人撤消络续学习的勇气。

指针径直对接内存结构,常见的C话语里面的指针乱指,数组越界根底原因等于内存问题。在指针这个点有源源络续的推崇空间。许多编程的时刻都在此连络。

指针还波及如何肯求开释内存,淌若开释不足时就会出现内存泄漏的情况,指针是高效好用,但不透顶搞阐明关于有些人来说真实等于恶梦。

在办法方面问题不错参见此前推文《关于C话语指针最详备的老师》,那么在指针方面不错参见一下大神的教养:

▎复杂类型证据

要了解指针,多些许少会出现一些相比复杂的类型。是以先先容一下如何完全融会一个复杂类型。

要融会复杂类型其实很圣洁,一个类型里会出现许多运算符,他们也像平常的抒发式相似,有优先级,其优先级和运算优先级相似。

是以笔者转头了一下其原则:从变量名处起,笔据运算符优先级衔尾,一步一步分析。

底下让咱们先勤俭单的类型脱手渐渐分析吧。

 int p;

这是一个平常的整型变量

 int p;

最初从P处脱手,先与衔尾,是以证据P是一个指针。然后再与int衔尾,证据指针所指向的内容的类型为int型,是以P是一个复返整型数据的指针

 int p[3];

最初从P处脱手,先与[]衔尾,证据P是一个数组。然后与int衔尾,证据数组里的元素是整型的,是以P是一个由整型数据构成的数组。

 int *p[3];

最初从P处脱手,先与[]衔尾,因为其优先级比高,是以P是一个数组。然后再与衔尾,证据数组里的元素是指针类型。之后再与int衔尾,证据指针所指向的内容的类型是整型的,是以P是一个由复返整型数据的指针所构成的数组。

 int (*p)[3];

最初从P处脱手,先与衔尾,证据P是一个指针。然后再与[]衔尾(与"()"这步不错忽略,只是为了蜕变优先级),证据指针所指向的内容是一个数组。之后再与int衔尾,证据数组里的元素是整型的。是以P是一个指向由整型数据构成3个整数的指针。

 int **p;

最初从P脱手,先与*衔尾,证据P是一个指针。然后再与*衔尾,证据指针所指向的元素是指针。之后再与int衔尾,证据该指针所指向的元素是整型数据。由于二级指针以及更高等的指针少许用在复杂的类型中,是以背面更复杂的类型咱们就不研讨多级指针了,最多只研讨一级指针。

 int p(int);

从P处起,先与()衔尾,证据P是一个函数。然后参预()里分析,证据该函数有一个整型变量的参数,之后再与外面的int衔尾,证据函数的复返值是一个整型数据。

 Int (*p)(int);

从P处脱手,先与指针衔尾,证据P是一个指针。然后与()衔尾,证据指针指向的是一个函数。之后再与()里的int衔尾,证据函数有一个int型的参数,再与最外层的int衔尾,证据函数的复返类型是整型,是以P是一个指向有一个整型参数且复返类型为整型的函数的指针。

  int (p(int))[3];

不错先跳过,不看这个类型,过于复杂。从P脱手,先与()衔尾,证据P是一个函数。然后参预()里面,与int衔尾,证据函数有一个整型变量参数。然后再与外面的衔尾,证据函数复返的是一个指针。之后到最外面一层,先与[]衔尾,证据复返的指针指向的是一个数组。接着再与衔尾,证据数组里的元素是指针,临了再与int衔尾,证据指针指向的内容是整型数据。是以P是一个参数为一个整数据且复返一个指向由整型指针变量构成的数组的指针变量的函数。

说到这里也就差未几了。融会了这几个类型,其它的类型对咱们来说亦然小菜了。不外一般不会用太复杂的类型,那样会大大减小形式的可读性,请慎用。这上头的几种类型也曾饱胀咱们用了。

▎细说指针

指针是一个特等的变量,它里面存储的数值被讲明成为内存里的一个地址。

要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让咱们诀别证据。

先声明几个指针放着做例子:

(1)int*ptr;

(2)char*ptr;

(3)int**ptr;

(4)int(*ptr)[3];

(5)int*(*ptr)[4];

▎指针的类型

从语法的角度看,小伙伴们唯有把指针声明语句里的指针名字去掉,剩下的部分等于这个指针的类型。这是指针本身所具有的类型。

让咱们望望上述例子中各个指针的类型:

(1)intptr;//指针的类型是int

(2)charptr;//指针的类型是char

(3)intptr;//指针的类型是int

(4)int(ptr)[3];//指针的类型是int()[3]

(5)int*(ptr)[4];//指针的类型是int(*)[4]

如何样?找出指针的类型的形式是不是很圣洁?

▎指针所指向的类型

当通过指针来拜访指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。

从语法上看,小伙伴们只需把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的等于指针所指向的类型。

上述例子中各个指针所指向的类型:

(1)intptr; //指针所指向的类型是int

(2)char*ptr; //指针所指向的的类型是char*

(3)int*ptr; //指针所指向的的类型是int*

(4)int(*ptr)[3]; //指针所指向的的类型是int(*)[3]

(5)int*(*ptr)[4]; //指针所指向的的类型是int*(*)[4]

在指针的算术运算中,指针所指向的类型有很大的作用。

指针的类型(即指针本身的类型)和指针所指向的类型是两个办法。当小伙伴们对C 越来越练习时,就会发现,把与指针搅和在全部的"类型"这个办法分红"指针的类型"和"指针所指向的类型"两个办法,是醒目指针的关键点之一。

笔者看了不少书,发现存些写得差的书中,就把指针的这两个办法搅在全部了,是以看起书来反覆无常,越看越吞吐。

▎指针的值

即指针所指向的内存区或地址。

指针的值是指针本身存储的数值,这个值将被编译器动作一个地址,而不是一个一般的数值。

在32位形式里,系数类型的指针的值都是一个32位整数,因为32位形式里内存地址绝对是32位长。指针所指向的内存区等于从指针的值所代表的阿谁内存地址脱手,长度为si zeof(指针所指向的类型)的一派内存区。

以后,咱们说一个指针的值是XX,就相配于说该指针指向了以XX为首地址的一派内存区域;咱们说一个指针指向了某块内存区域,就相配于说该指针的值是这块内存区域的首地址。

指针所指向的内存区和指针所指向的类型是两个完全不同的办法。在例一中,指针所指向的类型也曾有了,但由于指针还未开动化,是以它所指向的内存区是不存在的,或者说是无好奇的。

以后,每遭遇一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了那儿?

▎指针本身所占据的内存区

指针本身占了多大的内存?唯灵验函数sizeof(指针的类型)测一下就澄莹了。在32位平台里,指针本身占据4个字节的长度。指针本身占据的内存这个办法在判断一个指针抒发式是否是左值时很灵验。

02. 函数办法

面向历程对象模块的基本单元,以及对应多样组合,函数指针, 十七岁日本在线观看完整版指针函数

一个函数等于一个业务逻辑块,是面向历程,单元模块的最小单元,而且在函数的推论历程中,形参,实参如何交换数据,如何将数据传递出去,如何缠绵一个合理的函数,不只单是管制一个功能,还要看是不是冒昧复用,幸免重迭造轮子。

函数指针和指针函数,名义是两个字面好奇的互换骨子上含义霄壤之别,指针函数相比好融会,等于复返指针的一个函数,函数指针这个主要用在回调函数,许多人认为函数都没还搞阐明,回调函数更晕菜了。其实不错庸碌的融会指向函数的指针,本身是一个指针变量,只不外在开动化的时候指向了函数,这又回到了指针层面。没搞阐明指针再次真切的上前走终点难。

C话语的拓荒者们为自后的拓荒者做了一些省力气的事情,他们编写了无数代码,将常见的基本功能都完成了,不错让他人径直拿来使用。可是那么多代码,如何从中找到我方需要的呢?将系数代码都拿来阐明是不太履行。

可是这些代码,早已被早期的拓荒者们比物连类地放在了不同的文献中,况且每一段代码都有惟一的名字。是以其实学习C话语并莫得那么难,尤其是不错在入手进修做面孔中进行。使用代码时,唯有在对应的名字背面加上( )就不错。这样的一段代码等于函数,函数冒昧独飞速完成某个功能,一次编写完成后不错屡次使用。

许多初学者可能都会把C话语中的函数和数学中的函数办法搞污染。其实真相并莫得那么复杂,C话语中的函数是有规定可循迹的,唯有搞明晰了办法你会发现还挺专门思的。

函数的英文称号是 Function,对应翻译过来的汉文还有“功能”的好奇。C话语中的函数也跟功能有着密切的筹商。

咱们来看一小段C话语代码: 

#include<stdio.h>  int main()  {  puts("Hello World");  return 0;  } 

把眼神放在第4行代码上,这行代码会在线路器上输出“Hello World”。前边咱们也曾讲过,puts 背面要带( ),字符串也要放在( )中。

在C话语中,有的语句使用时不成带括号,有的语句必须带括号。带括号的等于函数(Function)。

C话语提供了许多功能,咱们只需要一句圣洁的代码就冒昧使用。可是这些功能的底层都相比复杂,频繁是软件和硬件的衔尾,还要要研讨许多细节和鸿沟,淌若将这些功能都交给形式员去完成,那将极大加多形式员的学习资本,裁汰编程成果。

有了函数之后,C话语的编程成果就好像有了神器相似,拓荒者们只需要随时调用就不错了,像程度函数、操作函数、时辰日历函数等都不错匡助咱们直领受场C话语本身的功能。

C话语函数是不错重迭使用的。

函数的一个显然特征等于使用时必须带括号( ),必要的话,括号中还不错包含待处理的数据。举例puts("尚观科技")就使用了一段具有输出功能的代码,这段代码的名字是 puts,"尚观科技" 是要交给这段代码处理的数据。使用函数在编程中有专科的称呼,叫做函数调用(Function Call)。

淌若函数需要处理多个数据,那么它们之间使用逗号,分隔,举例:

pow(10, 2);

该函数用来求10的2次方。

好了,看到这里你有莫得认为其实C话语函数照旧相比专门思的,而且并莫得那么复杂贫苦。以后再遭遇菜鸟小白的时候,天天摸夜夜添添到高潮水汪汪你一口一个C话语的函数,说不定就能就地引来无数跪拜的眼神。

03. 结构体,递归

许多在大学学习C话语的,许多课程都没学完,结构体都没学到,因为从章节的安排来看好像,结构体学习放在讲义的后半部分了,弄得许多学生认为结构体不紧迫,淌若只是应答学校的磨炼,或者等于为了混个毕业证,委果学的好奇不大。

淌若想从事编程这个行业,对这个办法还不了解,基本上无法构造数据模子,莫得一个业务体是完全使用原生数据类型来完成的,许多能手在缠绵数据模子的时候,一般先把头文献中的结构体数据整理出来。然后缠绵好功能函数的参数,以及名字,然后才信得过脱手写c源码。

淌若从量入计出空间研讨结构体里面的数据放的形式不相似在内存中占用的空间也不相似,结构体与结构体之间赋值,结构体存在指针那么赋值要终点缜密,需要进行深度的赋值。

递归一般用于从新到位统计或者摆设一些数据,在使用的时候许多初学者都认为别扭,如何还能我方调用我方?而且在使用的时候,一定诞生好跳出的条目,否则无停止的进行下去,真就成无线死轮回了。

关于结构体方面的学问,不错参见此前推送的著述《C话语结构体(struct)最全的老师(万字干货)》。具体也不错参见大佬的教养:

敬佩世界关于结构体都不生疏。在此,共享出自身对C话语结构体的磋商和学习的转头。淌若你发现这个转头中有你以前所未掌握的,那本文也算是有点价值了。虽然,水平有限,若发现不足之处恳请指出。代码文献test.c我放不才面。

在此,我会围绕以下2个问题来分析和操纵C话语结构体:

1. C话语中的结构体有何作用

2. 结构体成员变量内存对齐有何谨慎(重心)

关于一些办法的证据,我就不把C话语讲义上的界说搬上来。咱们坐下来渐渐聊吧。

1. 结构体有何作用

三个月前,教研室里一个学长在华为南京磋商院的口试中就遭遇这个问题。虽然,这只是口试中最基础的问题。淌若问你你如何修起?

我的融会是这样的,C话语中结构体至少有以下三个作用:

(1) 有机地组织了对象的属性。

比如,在STM32的RTC拓荒中,咱们需要数据来暗示日历和时辰,这些数据频繁是年、月、日、时、分、秒。淌若咱们无谓结构体,那么就需要界说6个变量来暗示。这样的话形式的数据结构是松散的,咱们的数据结构最佳是“高内聚,低耦合”的。是以,用一个结构体来暗示更好,不管是从形式的可读性照旧可移植性照旧可人戴性皆是:

typedef struct //公历日历和时辰结构体 

{  vu16 year;  vu8 month;  vu8 date;  vu8 hour;  vu8 min;  vu8 sec;  }_calendar_obj;  _calendar_obj calendar; //界说结构体变量 

(2) 以修改结构体成员变量的形式代替了函数(进口参数)的从新界说。

淌若说结构体有机地组织了对象的属性暗示结构体“中看”,那么以修改结构体成员变量的形式代替函数(进口参数)的从新界说就暗示了结构体“顶用”。络续以上头的结构体为例子,咱们来分析。假如当今我有如下函数来线路日历和时辰: 

void DsipDateTime( _calendar_obj DateTimeVal) 

那么咱们唯有将一个_calendar_obj这个结构体类型的变量作为实参调用DsipDateTime()即可,DsipDateTime()通过DateTimeVal的成变量来完毕内容的线路。淌若无谓结构体,咱们很可能需要写这样的一个函数: 

void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 hour,vu8 min,vu8 sec) 

阐明这样的形参很不可观,数据结构管制起来也很繁琐。淌若某个函数的复返值得是一个暗示日历和时辰的数据,那就更复杂了。这只是一方面。

另一方面,淌若用户需要暗示日历和时辰的数据中还要包含星期(周),这个时候,淌若之前没灵验机构体,那么应该在DsipDateTime()函数中在加多一个形参vu8 week: 

void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 week,vu8 hour,vu8 min,vu8 sec) 

可见这种形式来传递参数相配繁琐。是以以结构体作为函数的进口参数的平正之一等于函数的声明void DsipDateTime( _calendar_obj DateTimeVal)不需要蜕变,只需要加多结构体的成员变量,然后在函数的里面完毕上对calendar.week作相应的处理即可。这样,在形式的修改、爱戴方面作用显耀。 

typedef struct //公历日历和时辰结构体  {  vu16 year;  vu8 month;  vu8 date;  vu8 week;  vu8 hour;  vu8 min;  vu8 sec;  }_calendar_obj;  _calendar_obj calendar; //界说结构体变量 

(3) 结构体的内存对齐原则不错进步CPU对内存的拜访速率(以空间换取时辰)。

况且,结构体成员变量的地址不错笔据基地址(以偏移量offset)计较。咱们先来望望底下的一段圣洁的形式,关于此形式的分析会在第2部分结构体成员变量内存对齐中缜密证据。 

#include<stdio.h>  int main()  {      struct    //声明结构体char_short_long      {          char  c;          short s;          long  l;      }char_short_long;     struct    //声明结构体long_short_char      {          long  l;          short s;          char  c;      }long_short_char;      struct    //声明结构体char_long_short      {         char  c;          long  l;          short s;      }char_long_short;  printf(" \n");  printf(" Size of char   = %d bytes\n",sizeof(char));  printf(" Size of shrot  = %d bytes\n",sizeof(short));  printf(" Size of long   = %d bytes\n",sizeof(long));  printf(" \n");  //char_short_long  printf(" Size of char_short_long       = %d bytes\n",sizeof(char_short_long));  printf("     Addr of char_short_long.c = 0x%p (10进制:%d)\n",&char_short_long.c,&char_short_long.c);  printf("     Addr of char_short_long.s = 0x%p (10进制:%d)\n",&char_short_long.s,&char_short_long.s);  printf("     Addr of char_short_long.l = 0x%p (10进制:%d)\n",&char_short_long.l,&char_short_long.l);  printf(" \n");  printf(" \n");  //long_short_char  printf(" Size of long_short_char       = %d bytes\n",sizeof(long_short_char));  printf("     Addr of long_short_char.l = 0x%p (10进制:%d)\n",&long_short_char.l,&long_short_char.l);  printf("     Addr of long_short_char.s = 0x%p (10进制:%d)\n",&long_short_char.s,&long_short_char.s);  printf("     Addr of long_short_char.c = 0x%p (10进制:%d)\n",&long_short_char.c,&long_short_char.c);  printf(" \n");  printf(" \n");  //char_long_short  printf(" Size of char_long_short       = %d bytes\n",sizeof(char_long_short));  printf("     Addr of char_long_short.c = 0x%p (10进制:%d)\n",&char_long_short.c,&char_long_short.c);  printf("     Addr of char_long_short.l = 0x%p (10进制:%d)\n",&char_long_short.l,&char_long_short.l);  printf("     Addr of char_long_short.s = 0x%p (10进制:%d)\n",&char_long_short.s,&char_long_short.s);  printf(" \n");  return 0;  } 

形式的运行适度如下(缜密:括号内的数据是成员变量的地址的十进制体式):

2. 结构体成员变量内存对齐

最初,咱们来分析一下上头形式的运行适度。前三行证据在我的形式中,char型占1个字节,short型占2个字节,long型占4个字节。char_short_long、long_short_char和char_long_short是三个结构体成员疏通可是成员变量的排列形式不同。况且从形式的运行适度来看,  

Size of char_short_long = 8 bytes  Size of long_short_char = 8 bytes  Size of char_long_short = 12 bytes //比前两种情况大4 byte !  

况且,还要缜密到,1 byte (char)+ 2 byte (short)+ 4 byte (long) = 7 byte,而不是8 byte。

是以,结构体成员变量的摈弃形式影响着结构体所占的内存空间的大小。一个结构体变量所占内存的大小不一定等于其成员变量所占空间之和。淌若一个用户形式或者操作系统(比如uC/OS-II)中存在无数结构体变量时,这种内存占用必须要进行优化,也等于说,结构体里面成员变量的排列顺序是有谨慎的。

结构体成员变量到底是如何存放的呢?

在这里,我就不卖关子了,径直给出如下论断,在莫得#pragma pack宏的情况下:

原则1 结构(struct或斡旋union)的数据成员,第一个数据成员放在offset为0的所在,以后每个数据成员存储的肇始位置要从该成员大小的整数倍脱手(比如int在32位机为4字节,则要从4的整数倍地址脱手存储)。

原则2 结构体的总大小,也等于sizeof的适度,必须是其里面最大成员的整数倍,不足的要补齐。

*原则3 结构体作为成员时,结构体成员要从其里面最大元素大小的整数倍地址脱手存储。(struct a里存有struct b,b里有char,int,double等元素时,那么b应该从8的整数倍地址处脱手存储,因为sizeof(double) = 8 bytes)

这里,咱们衔尾上头的形式来分析(暂时不参谋原则3)。

先望望char_short_long和long_short_char这两个结构体,从它们的成员变量的地址不错看出来,这两个结构体合适原则1和原则2。缜密,在 char_short_long的成员变量的地址中,char_short_long.s的地址是1244994,也等于说,1244993是“空的”,只是被“占位”了!

再望望char_long_short这个结构体,char_long_short的地址散布情况如下表:

成员变量 成员变量十六进制地址 成员变量十进制地址 char_long_short.c 0x0012FF2C 1244972 char_long_short.l 0x0012FF30 1244976 char_long_short.s 0x0012FF34 1244980

可见,其内存散布图如下,共12 bytes:

地址 1244972 1244973 1244974 1244975 1244976 1244977 1244978 1244979 1244980 1244981 1244982 1244983 成员 .c       .l .s    

最初,1244972能被1整除,是以char_long_short.c放在1244972处莫得问题(其实,就char型成员变量自身来说,其放在职何地址单元处都莫得问题),笔据原则1,在之后的1244973~1244975中都莫得能被4(因为sizeof(long)=4bytes)整除的,1244976能被4整除,是以char_long_short.l应该放在1244976处,那么同理,临了一个.s(sizeof(short)=2 bytes)是应该放在1244980处。

是不是这样就闭幕了?不是,还有原则2。笔据原则2的要求,char_long_short这个结构体所占的空间大小应该是其占内存空间最大的成员变量的大小的整数倍。淌若咱们到此就闭幕了,那么char_long_short所占的内存空间是1244972~1244981统共10bytes,不合适原则2,是以,必须在临了补齐2个 bytes(1244982~1244983)。

至此,一个结构体的内存布局完成了。

底下咱们按照上述原则,来考据这样的分析是不是正确。按上头的分析,地址单元1244973、1244974、1244975以及1244982、1244983都是空的(至少char_long_short未用到,只是“占位”了)。淌若咱们的分析是正确的,那么,界说这样一个结构体,其所占内存也应该是12 bytes: 

struct //声明结构体char_long_short_new  {  char c;  char add1; //补齐空间  char add2; //补齐空间  char add3; //补齐空间  long l;  short s;  char add4; //补齐空间  char add5; //补齐空间  }char_long_short_new; 

运行适度如下:

可见,咱们的分析是正确的。至于原则3,世界不错我方编程考据,这里就不再参谋了。

是以,不管你是在VC6.0照旧Keil C51,照旧Keil MDK中,当你需要界说一个结构体时,唯有你略微得当结构体成员变量内存对齐这一表象,就不错在很大程度上勤俭MCU的RAM。这一丝不只是操纵于骨子编程,在许多大型公司,比如IBM、微软、百度、华为的笔试和口试中,亦然常见的。

这三大块硬骨头是学习C话语的绊脚石,下功夫拿掉基本上C话语的大动脉就买通了,那么再去学习别的内容就相对相比圣洁了。编程学习历程中越是可怜的时候,学到的东西就会越多,克服夙昔就会我方的手段,撤消了前边的付出的时辰都将清零。越是难学的话语在初学之后,在初学之后越认为过瘾,而且还容易上瘾。你上瘾了没?照旧撤消了? 

 





Powered by 东北女人毛多水多牲交视频 @2013-2022 RSS地图 HTML地图