C语言学习笔记

目录

 一、#define

二、小端模式和大端模式

三、union的用法

四、整数变量的值移位时要注意的问题

五、memcpy()函数的用法

0​​​​​​2(71条消息) C函数之memcpy()函数用法_冀博的博客-CSDN博客_memcpy返回值

2022.8.1

六、字符型指针

2022.7.26号更新

 EXAMPLE_DEVICE_NAME是字符串,类型默认是char*类型

 七、‘\0’和“0”和‘0’和0的区别

八、break可以跳出最近的循环

2022.7.23号更新

九、&&与&的最大区别

十、&作为取址符注意的问题​编辑

十一、malloc()函数的用法​编辑

十二、strstr函数的概念

十三、sizeof()和strlen()函数有什么区别

十四、extern的用法

 十五、用宏定义写一个函数

 十六、输出格式

  十七、sprintf函数的用法​编辑

十八、结构体成员不能赋初值。

 注意:结构体成员不能赋初值。

十九、volatile关键字的使用

二十、c语言中堆栈指针的位置,C语言及ARM中堆栈指针SP设置的理解与总结

二十一、\r\n和\n\r是不一样的

二十二、printf函数如果将一个浮点型的数据输出%d时,数据就会让人看不懂

二十三、虚拟内存

二十四、内存分布图

二十五、static的用法

二十六、text段在程序运行过程中只能读,不能写

二十七、C语言中的const并不是只能读的,也可以通过指针强制改写

 ​编辑​编辑

 二十八、动态存储分配

二十九、全局变量在定义的时候一般初始化为0(只针对DSP芯片,RAM芯片是可以初始化为非0值)。

 三十、数组名不能自加或自减

三十二、条件编译

三十三、 用枚举类型替换switch

三十四、提高代码运行效率的语句

1、求余运算

2、平方运算

3、用移位实现乘除法运算

4、避免不必要的整数除法

5、使用增量和减量操作符

6、结构体成员的布局

7、按数据类型的长度排序本地变量

8、延时函数

三十五、浮点数的比较

三十六、浮点数的赋值

三十七、局部变量定义时需要初始化为0,否则里面可能是一个垃圾值。

尾部增添成员

栈的引用




 一、#define

C语言中,可以用 #define 定义一个标识符来表示一个常量。其特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。

预编译又叫预处理。预编译不是编译,而是编译前的处理,编译器正式开始编译程序之前,会执行一段预处理程序(又称预处理器)专门对程序执行预处理操作。

C语言程序从编写到运行要经过预处理、编译、汇编和链接这 4 个阶段,但大多数人习惯将前 3 个阶段统称为编译阶段,所以才有了“程序要经过编译和链接后才能运行”的说法。

二、小端模式和大端模式

三、union的用法

1、

 

 错误的认为all变量和bit变量是相互独立的,其实all变量就包含了全部的bit变量了。

同理如下:

union 的概念及在嵌入式编程中的应用 - wenzid - 博客园 (cnblogs.com)

2、

 

如果通信双方规定使用整型来解析的话 ,那我发送的时候就不能按浮点数格式进行发送,而应该按整型数据格式发送。

假设我按浮点数格式发送,假设发送的是15.0,15.0的16进制数是0x41700000。 

 

四、整数变量的值移位时要注意的问题

这个语句如果都是整型变量的话就不能理解成ucNumOfBlock=(NumByteToWrite-1)>>sEE_BLOCKZIZE_Shift,浮点类型才可以。

五、memcpy()函数的用法

0​​​​​​2(71条消息) C函数之memcpy()函数用法_冀博的博客-CSDN博客_memcpy返回值

2022.8.1

%x即按十六进制输出,英文字母小写,右对齐。
%02X有以下变化:英文字母变大写,如果输出字符不足两位的,输出两位宽度,右对齐,空的一位补0。超过两位的,全部输出。

六、字符型指针

2022.7.26号更新

 

 EXAMPLE_DEVICE_NAME是字符串,类型默认是char*类型

而P_LEN是unsigned char*类型的,正确方法如下:

 七、‘\0’和“0”和‘0’和0的区别

首先比较一下‘\0’和‘0’的区别。有一个共同点就是它们都是字符,在c语言中,字符是按其所对应的ASCII码来存储的,一个字符占一个字节。请看第一个ASCII码,对是0,对应的字符是(Null),其实就是 ‘\0’,即空字符。判断一个字符串是否结束的标志就是看是否遇到‘\0’,如果遇到‘\0’,则表示字符串结束。而字符‘0’对应的ASCII码是 48,48对应的十六进制数就是0x30。比如要将8转换为字符8,在语句中这样写就可以 了,“ 8+‘0’”。这里的8就是数字。字符‘0’和数字0的区别:前者是字符常量,后者是整形常量,它们的含义和在计算机中的存储方式截然不同。但是字符常量 可以像整数一样在程序中参与相关运算。例如:‘9’-3;。哈哈,是不是柳暗花明又一村啊。

   接下来我们比较一下“0”和‘0’的区别。首先“0”是字符串常量,字符串常量是由一对双引号括起的字符序列。例如:“CHINA”,“I LOVE YOU”,“123”等都是合法的字符串常量。‘0’是字符常量,字符串常量和字符常量是不同的量。1:字符常量由单引号括起来;字符串常量由双引号括起来。2:字符常量只能是单个字符字符串常量则可以含一个或多个字符

八、break可以跳出最近的循环

2022.7.23号更新

1、u8 *cmd="AT";

printf("%s",cmd);//可以输出AT

2、while(1)

{

break;

}//break可以退出最近的一个while循环。

九、&&与&的最大区别

&&是逻辑与,只会返回0和1。

十、&作为取址符注意的问题

 

do_crc(&p_data->DEVNO,7);//一定要加&符号 

十一、malloc()函数的用法

 mymalloc函数在malloc.h头文件中有定义,SRAMIN是内部内存池,SRAMEX是外部内存池。

在内存中定义一个动态堆区,并把堆空间的首地址给指针p。一般开辟这段内存是存放临时的数据。

十二、strstr函数的概念

strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回NULL。

十三、sizeof()和strlen()函数有什么区别

int main()
{
    char *p = "hello";
    char q[] = "hello";
    char s[10] = "hello";
    printf("%d, %d, %d\n", sizeof(p), sizeof(*p), sizeof(q), sizeof(s));

    return 0;
}

输出结果为:4, 1, 6, 10
p是一个指针,sizeof(p)得到的是指针占用的字节数,所以sizeof(p)结果为4,p是指向“hello”的指针,*p是“hello”的第一个字符h,是char类型,sizeof(*p)得到结果为1,char q[]未指定数组大小,根据“hello”来确定,“hello”包括空字符‘\0’总共有6个字节,所以sizeof(q)结果为6。char s[10]指定了数组大小,是一个包含10个char字符的数组,所以sizeof(s)结果为10。

下面这句语句是错的:
char q[5] = "hello"; //"hello"是const char[6]类型的,不能用于初始化char[5]类型的实体。

总之,对指针来说,sizeof返回这个指针所占的空间大小,一般为4个字节,对数组来说,sizeof返回这个数组中所有的元素占的总空间大小。char q[]占用空间大小由指向的字符串或字符数组大小来确定,char q[10]占用空间大小由指定的数组大小确定。

char *p = "hello";
char q[] = "hello";
char s[10] = "hello";

strlen(p), strlen(q), strlen[s]返回结果都是5,strlen(p)参数是字符型指针(char *),返回字符串长度,不包括‘\0‘。

十四、extern的用法

已经在lcd.c文件下面包含了lcdfont.h文件,所以在main函数包含这个头文件就会显示重复定义,此时又因为main函数也需要用到lcdfont.h中的数组,因此可以采用extern这个关键字。

假设b.c文件下面定义了  int  b;这个语句,那么在a.c文件中要想用到这个变量的话可以1)直接extern  int b,但是不推荐这一种方法;

  1. 也可以在b.h头文件中声明extern  int  b,直接调用这个头文件即可。因为有可能b文件是你的同事写的,为了更好的协调配合,你的同事都把b文件的全局变量都声明为外部变量,方便你的使用。

 在a.c中定义了extern int b;那么当编译的时候就会生成a.o文件,但并不会去找b这个变量在哪里,而是把它用symbol标记起来,同时b.c文件也会编译,b.c文件中有定义了变量b,当两个文件都编译后就会链接起来,就能找到b变量了。

用一个GlobalVariables.h文件来声明所有全局变量的外部使用。

 比如在InitData.c文件中要调用vRrpSendNull函数,但该函数是在User_ECan.c文件中被定义的,

只要User_ECan.c文件和InitData.c文件都包含#include "GlobalVariables.h"即可。

总结:一个工程需要有一个.h文件去存放所有全局变量的外部使用。

 

 

 

 十五、用宏定义写一个函数

 十六、输出格式

%p”中的p是pointer(指针)的缩写。%p是打印地址的, 而%x是以十六进制形式打印。
%p是打印地址(指针地址)的,是十六进制的形式,但是会全部打完,即有多少位打印多少位。

32位编译器的指针变量为4个字节(32位),64位编译器的指针变量为8个字节(64位)。

所以,在32位编译器下,使用%p打印指针变量,则会显示32位的地址(16进制的);在64位编译器下,使用%p打印指针变量,则会显示64位的地址(16进制的),左边空缺的会补0。

%x无符号十六进制整数(字母小写,不像上面指针地址那样补零)

%X无符号十六进制整数(字母大写,不像上面指针那样补零)

%x、%X和%p的相同点都是16进制,不同点是%p按编译器位数长短(32位/64位)输出地址,不够

 
 十七、sprintf函数的用法

sprintf的作用是将一个格式化的字符串输出到一个目的字符串中,而printf是将一个格式化的字符串输出到屏幕。

但是在keil中不能省略char*类型

十八、结构体成员不能赋初值。

 

 

 注意:结构体成员不能赋初值。

十九、volatile关键字的使用

 

 

 一般访问硬件寄存器都需要用volatile关键字,确保每次都能访问的是外设中的寄存器,而不是Cache

二十、c语言中堆栈指针的位置,C语言及ARM中堆栈指针SP设置的理解与总结

 

 

 

 

二十一、\r\n和\n\r是不一样的

 

 在printf函数中打印\n\r跟没打印一样,打印\r\n才会回车和换行。

二十二、printf函数如果将一个浮点型的数据输出%d时,数据就会让人看不懂

二十三、虚拟内存

如果我们运行的程序较多,占用的空间就会超过内存(内存条)容量。例如计算机的内存容量为2G,却运行着10个程序,这10个程序共占用3G的空间,也就意味着需要从硬盘复制 3G 的数据到内存,这显然是不可能的。

操作系统(Operating System,简称 OS)为我们解决了这个问题:当程序运行需要的空间大于内存容量时,会将内存中暂时不用的数据再写回硬盘;需要这些数据时再从硬盘中读取,并将另外一部分不用的数据写入硬盘。这样,硬盘中就会有一部分空间用来存放内存中暂时不用的数据。这一部分空间就叫做虚拟内存(Virtual Memory)。

3G - 2G = 1G,上面的情况需要在硬盘上分配 1G 的虚拟内存。

硬盘的读写速度比内存慢很多,反复交换数据会消耗很多时间,所以如果你的内存太小,会严重影响计算机的运行速度,甚至会出现”卡死“现象,即使CPU强劲,也不会有大的改观。如果经济条件允许,建议将内存升级为 4G,在 win7、win8、win10 下运行软件就会比较流畅了。


二十四、内存分布图

 对于一个C语言程序而言,内存空间主要由五个部分组成代码段(.text)、数据段(.data)、BSS段(.bss),堆和栈组成,其中代码段,数据段和BSS段是编译的时候由编译器分配的,而堆和栈是程序运行的时候由系统分配的

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中。

BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (数据段和BSS段都是存放全局变量和静态变量,但是BSS段存放的是未初始化的全局变量和静态变量,未初始化的意思就是变量刚开始没有被赋值,比如int A;这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。全局变量在程序运行前就分配好了。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间而只在运行的时候占用内存空间,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。字符串常量等,但一般都是放在只读数据段中。

注意:所有的字符窜常量都被放在静态内存区中的只读数据段(只读数据段其实也是在text段)
因为字符串常量很少需要修改,放在静态内存区会提高效率

 

 当我们在printf函数的字符串前面添加“1234”,查看编译前后text段的变化。

 

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的`变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。注意:栈空间是向下增长的,每个线程有一个自己的栈,在Linux上默认的大小是8M,可以用ulimit查看和修改。

二十五、static的用法

静态全局变量
1、特点:存储在静态存储区,能在整个程序运行期间存在,但只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用,即作用域局限于一个源文件内
2、区别:静态全局变量如果未经初始化会被程序自动初始化为0,而普通全局变量未经初始化的值是任意的;普通全局变量作用域在整个工程中,全局静态变量作用域只在声明它的文件下可见(所以static声明的函数和变量不能在另一个文件中引用,也就是说,如果加了 static,就会对其它源文件隐藏,无法进行调用。)
3、作用:可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突,static全局变量只初使化一次,也不会被其他文件访问和修改。

静态局部变量
1、特点:静态局部变量在函数内定义,当定义它的函数结束时,作用域结束。(静态变量离开作用域后并没有被销毁,仍然驻留在内存中,知道程序结束,但不能对其访问)
2、区别:静态局部变量如果未经初始化会被程序自动初始化为0,而普通全局变量未经初始化的值是任意的;
3、作用:局部变量再次调用定义它的函数时,可继续使用,当多次调用一个函数且要求在调用之间保留某些变量的值时,可采用静态局部变量。
 

int fun1()
{
static int s_value = 0;
….
}

fun1不管在什么地方被调用,当函数退出后,s_value最后的值将一直会被系统保存(相当于一个全局变量),下次s_value再被用到时,也即当fun1()再次被调用时,s_value初始值将是最近被保存过的值(请注意s_value初始化操作只会被执行一次,即上述s_value =0 这个语句)。

 在C语言中static的作用如下:
第一、在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
第二、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
第三、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0;

 

 

二十六、text段在程序运行过程中只能读,不能写

 

二十七、C语言中的const并不是只能读的,也可以通过指针强制改写

 

 

 二十八、动态存储分配

从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存。malloc与free是配对使用的, free能释放堆空间。

void * malloc(size_t num)

void   free(void *p)

1.malloc分配一块连续的内存空间

char *p;
p = (char *)malloc(10*sizeof(char));

malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。 所以这里要转换成所需要的指针类型。

2.判断是否成功申请到空间

	if(p == NULL)
	{
		printf("malloc failed\n");
		return 0;
	}

  malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。

3.使用完记得free,释放内存

free(p);

 如果free的参数是NULL的话,没有任何效果。

释放一块内存中的一部分是不被允许的(比如free(p+1)这是不行的)。

注意事项:

1.free(p); 释放堆空间,并没有删除p指针。释放堆空间后,p成了空悬指针。如果不使用这个指针,请将指针置空 p = NULL;

2.动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

3.malloc与free是配对使用的, free只能释放堆空间。如果malloc返回的指针值丢失,则内存泄漏
 

二十九、全局变量在定义的时候一般初始化为0(只针对DSP芯片,RAM芯片是可以初始化为非0值)这种说法其实是错误的。

我以前的认知:

1、

 

 

2、

 3、静态局部变量

我现在的认知:

全局变量在初始化的时候如果不赋值的话,其实对应的值不一定是0,而是之前的垃圾值。

只是在程序执行到vClrRam函数时把RAM区域(里面存放全局变量)全部清零了。

 三十、数组名不能自加或自减

 

 三十一、发送n个字节数据,如何实现连续4个数据发出?

将SPI_WR_MAX_Mul宏定义为0.249,假设发送4个字节,整型数据4*0.249还是整数0。

假设要发送一堆数据,先将这堆数据按四个字节数据为一组进行分组,通过一堆数据*0.0249就可以得到组数uiNumWrite(从0开始),如果组数为0,就说明这堆数据小于4个字节。如果组数为n,就循环n次每次将数据按四个发出去。循环到n小于1时,就将最后剩余的最多四字节的数据发出去。

三十二、条件编译

三十三、 用枚举类型替换switch

1、用switch

 2、枚举用法

 

 

 MON、TUE、WED 这些名字都被替换成了对应的数字。这意味着,MON、TUE、WED 等都不是变量,它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存,而是直接被编译到命令里面,放到代码区,所以不能用&取得它们的地址。这就是枚举的本质。

三十四、提高代码运行效率的语句

1、求余运算

a=a%8;

可以改成

a=a&7;

位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n方的余数,均可使用位操作的方法来代替。

2、平方运算

a=pow(a,2.0);

可以改成

a=a*a;

因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。

3、用移位实现乘除法运算

a = a * 4;
b = b / 4;

可以改为:

a = a << 2;
b = b >> 2;
a = a * 9;

可以改为:

a = ( a << 3 ) + a;

4、避免不必要的整数除法

整数除法是整数运算中最慢的,应该尽可能避免。可以由乘法替代。

int i , j , k , m ;
m =i / j / k ; 

可以改为:

int i , j , k , m ;
m = i / ( j * k );

5、使用增量和减量操作符

使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快。

x=x+1;

可以改成:

x++;

6、结构体成员的布局

在声明一个复杂的数据类型(既有多字节数据又有单字节数据)时,应该首先存放多字节数据,然后再存放单字节数据,这样可以避免内存的空洞。

struct 

{

char a [5];

long k ;

double x;

}baz;

可以改成:

struct 

{

double x;

long k;

char a[5];

char pad [7];

}baz;

7、按数据类型的长度排序本地变量

当编译器分配给本地变量空间时,应该把长的变量放在短的变量前面。

short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;

可以改为:

double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;

8、延时函数

void delay (void)
{
  unsigned int i;
  for (i=0;i<1000;i++) ;
}

可以改为:

void delay (void)
{
  unsigned int i;
  for (i=1000;i>0;i--) ;
}

两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一种代码少1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能够生成这类指令。

三十五、浮点数的比较

#define EPSILON 0.000001 //根据精度需要
if ( fabs( fa - fb) < EPSILON )
{
printf("fa<fb\n");
}

计算机表示浮点数(float或double类型)都有一个精度限制,对于超出了精度限制的浮点数,计算机会把它们的精度之外的小数部分截断。因此,本来不相等的两个浮点数在计算机中可能就变成相等的了。
例如:

float a=10.222222225,b=10.222222229
数学上 a 和 b 是不相等的,但在32 位计算机中它们是相等的。
如果两个同符号浮点数之差的绝对值小于或等于某一个可接受的误差(即精度),就认为它们是相等的。
不要直接用 “==” 或者 “!=” 对两个浮点数进行比较,但是可以直接用 “<” 和 “>” 比较谁大谁小


三十六、浮点数的赋值

1)用浮点数类型不能直接赋值一个十六进制的值,只能赋值一个小数值,不然就会有问题。

 2)float fTmp=100.0和float fTmp=100其实没什么区别,因为最终赋予的值都是100.0。

三十七、局部变量定义时需要初始化为0,否则里面可能是一个垃圾值,其实全局变量也是一样的。

三十八、全局变量一旦定义,就会占用RAM空间,哪怕这个变量从来都没用过。

三十九、进行或运算需要注意运算优先级问题

这里一定要加括号,否则结果会不一样。

 下面这样也是可以的。

 下面这样就得不到想要的结果。

 四十、浮点数不能用于移位,如何把float类型的变量a假设为58.0(0x42560000)保存到Uint16 b[4]={0x0042,0x0056,0x0000,0x0000}中。

错误方法:

float a;
Uint16 i;
Uint16 b[4];                        
for(i=0;i<4;i++)
{
b[i++] = ((float)a>>8*(3-i))&0xff;
}

正确方法:

Uint32 *ucpData;
Uint16 b[4];
Uint16 i;
float a=58.0;
 ucpData= (Uint32*)&a;
  for(i=0;i<4;i++)
  {
    b[i++] = (*(ucpData)>>8*(3-i))&0x000000FF;
  }

 

12、 

 

 WriteData是一个数组的名称,也就是一个数组的首地址。

13、 

BaseType_t RET;

RET=xTaskCreate(task2,"Task2",100,NULL,1,&Task2_Handle);

上面这种写法就没问题

BaseType_t RET=xTaskCreate(task2,"Task2",100,NULL,1,&Task2_Handle);

这种写法就会报错。

14、定义数据类型是不占用内存空间的

 下面这条语句才会占用内存空间,只有定义了变量才会占用内存空间

15、结构体变量占用的内存空间(字节对齐)

总结:结构体的成员顺序不一样,会影响最终的结构体的大小;最终结构体的大小一定是4的倍数。

 占用8个字节。

 占用104个字节。

占用8个字节

CPU既可以通过偶地址也可以奇地址实现对单个字节的变量进行写,如果是对四个字节的变量进行写就需要从偶地址开始,并且是四个字节为一个整体进行访问(因为是32位处理器,这样访问效率最高)。

 占用8个字节。

 

 

 占用了104个字节

 

16、变量赋值需要注意的问题

 全局变量就会按单个字节分配char类型的变量。

 

 说明如果是局部变量的话,char类型的变量就会按四字节来分配地址,并且是按地址从高到低分配内存,只有栈才会这样分配。

 

 

17、 

 在main函数的下面定义了一个函数

 

在serial.c文件下面定义了一个函数

 结果打印出来的是main.c。说明函数名称相同的话优先执行本地文件。如果把static关键字去掉的话就会报错了,因为会显示重复定义函数。

 18、结构体多种常见写法

 

 

 19、结构体指针和函数指针

注意:结构体中的classmate变量所占的空间并不清楚,而且会无限套娃,因此不可以使用

 正确做法如下:

 结构体指针

int * add(int a,int b);

这个函数有两种意思,一种是返回值是int *类型,一种是定义了函数指针*add。

为了区分两种意思,应该进行括号分割,当表示返回值是int*类型时,(int * )add(int a,int b);当表示定义了函数指针时,int (* add)(int a,int b);

int (* add)(int a,int b);跟int * add;没有什么差别。int (* add)(int a,int b);并不是一个函数,而是一个变量,这个变量是一个函数指针。

 

 

 

 错误写法如下

 Ifdef即便判断到LCDA为0也会执行,因为有定义。

 执行的是LCD_A()

 

 

 

 

 

20、Typedef

typedef     1      A;是错误的

但是#define  A   1就是正确的

Typedef    int    A;

A i;

就是对的

因为typedef的第1个参数只能是类型,因为typedef本来字面意思就是定义类型的。

 左边的结构体可以由右边两个结构体组合而成。Lcd_operation是前面红框的别名,同理下面的结构体指针类型也是。

21、链表

 

 用一个while(Head)就很巧妙,因为最后一个节点的NEXT是指向NULL。

 

 

 

 

 

 

 

 链表改进

尾部增添成员

 

 

 

 

 删除任意元素(有可能要删除的元素并没有)

 从小到大排列

 通用链表

这种是之前的,采用的是上一个节点的next指向下一个节点的首元素的地址。

 

 改正如下:

 

 

 

 

 它是通过下面这种方法求出偏移地址

 将last->next转化为char*类型进行相减可以保证是按存储单元为字节数进行相减的。

21、通用链表的三种实现方法

 

 

得到了node结构体的地址就得到了person结构体的首地址。

 

 

 

 传入的是node_t的指针,由于这个指针是struct person下的node的成员,因此类型是struct person, 成员是node。

 

 

 双向链表

 

 

 

 

 

 

 

 

 

 22、FreeRTOS链表代码分析

 

 

 

uxNumberOfItems表示有多少个元素

pxIndex指向当前在用的Item,被用来遍历链表。

 

pvOwner是包含Item的结构体

pxContainer:链表

 

 Item初始化

 增加一个元素到尾部

 加入一个新元素必须加入到Index指向的item之前,也就是插入到Index->pxPrevious该item的后面。

 排序item

 

 

 

 

 22、ARM架构与汇编指令

 

23、全局变量的初始化

 

 在bin或者HEX文件里面有代码段和数据段,有值的全局变量就是保存在数据段里的,当烧录到Falsh里面时也是分为代码段和数据段,当程序运行时就会把Flash里面的数据段原封不动的拷贝到RAM中。

 

ZI段是RAM里面的一段内存空间,相当于menset函数,将未初始化或者初始化为0的全局变量的值都初始化为0。

等到有值的全局变量以及初始化为0、未初始化的全局变量都存放好之后就开始调用main函数了。

 Flash里面有代码段和数据段,当程序运行时,数据段会通过类似于mencpy函数的方式重定义到RAM中的Data段,这段空间用于存放有初值的全局变量;同时RAM中也划分出来ZI段,这段空间用于存放初值为0或者无初值的全局变量;接着SP指针会指向RAM中一段空闲的空间用于作为堆栈空间,最后main函数才开始调用。

栈的引用

 

 

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值