目录
摘要:普通变量、指针变量所占的内存空间大小,变量在内存中的分配方式(首地址要求、长度、分配长度与实际使用长度区别、需要填充字节数、全局与局部变量在内存中的位置);
摘要:外部(文件)变量的几种引用方式&建议方式、个人总结笔记
摘要:函数指针的本质,函数指针变量的定义方式、强制类型转换、使用方法,公司使用案例(如何使程序支持(并自动识别)多款(同类型,可相互替代)设备)
韦老师的整理笔记:
主题如下:
-
变量与指针
-
链表操作
-
ARM架构简述
-
几条汇编指令
-
结合汇编掌握:全局变量、局部变量、变量赋值、地址操作、栈等深层次的知识
git下载: 本教程所有资料放在如下GIT仓库里:
git clone https://e.coding.net/weidongshan/rtos_training/c_improve.git:
1.变量与指针
摘要:普通变量、指针变量所占的内存空间大小,变量在内存中的分配方式(首地址要求、长度、分配长度与实际使用长度区别、需要填充字节数、全局与局部变量在内存中的位置);
2个口诀: 变量变量,能变,就是能读能写,必定在内存里 指针指针,保存的是地址,32位处理器中地址都是32位的,无论是什么类型的指针变量,都是4字节
1.1例程
... volatile int a; volatile char c; volatile char buf[100]; volatile int *p; volatile char *p2; int main(void) { a=1; C='A'; buf[99]='B'; ... }
源码在开发板上编译(?)运行,生成的RTOSdemo.map中关于int 型和 char 型(全局)变量的内存占用大小如下:
总结:
.char/short/int/long/double/struct xx等所有数据类型的指针变量的内存大小,在32位系统下是4字节,在64位系统下都是8字节。
全局与局部分配内部时的(位置)区别
注1:视频中演示的.map文件的主要功能,是标志程序、数据、IO,在编译时和空间地址之间的映射关系。因此,该文件不能显示临时生成的局部变量的内存地址,但可以显示全局变量的内存地址。(即全局变量在源码的编译后的二进制代码时,已分配内存空间,而局部变量没有,局部变量需要在执行函数时在栈中临时分配。)
1.2我的测试
#include <stdio.h> struct StudentInfo{ char *name; int age; int high; }; struct StudentInfo2{ char num; int age; int high; }; volatile int val; volatile char c; volatile char buf[100]; volatile struct StudentInfo g_st; volatile struct StudentInfo2 g_st2; volatile int *p_i; //全局变量,volatile防止编译器因为变量未使用而自动优化去掉; volatile char *p_c; volatile short *p_s; volatile double *p_d; volatile struct StudentInfo *p_st; volatile struct StudentInfo2 *p_st2; int main(void) { printf("sizeof(char)=%d, sizeof(short)=%d, sizeof(int)=%d, sizeof(long)=%d\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long)); printf("sizeof(float)=%d, sizeof(double)=%d, sizeof(buf)=%d\n", sizeof(float), sizeof(double), sizeof(buf)); printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d\n", sizeof(struct StudentInfo), sizeof(struct StudentInfo2)); printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d\n", sizeof(g_st), sizeof(g_st2)); printf("sizeof(int *)=%d \n", sizeof(p_i)); //sizeof(p)=4; printf("sizeof(char *)=%d \n", sizeof(p_c)); printf("sizeof(short *)=%d \n", sizeof(p_s)); printf("sizeof(long *)=%d \n", sizeof(long)); printf("sizeof(double *)=%d \n", sizeof(p_d)); printf("sizeof(struct StudentInfo *)=%d \n", sizeof(p_st)); printf("sizeof(struct StudentInfo2 *)=%d \n", sizeof(p_st2)); return 0; } /* Microsoft Visual C++6.0测试结果,32位: sizeof(char)=1, sizeof(short)=2, sizeof(int)=4, sizeof(long)=4 sizeof(float)=4, sizeof(double)=8, sizeof(buf)=100 sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12 sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12 sizeof(int *)=4 sizeof(char *)=4 sizeof(short *)=4 sizeof(long *)=4 sizeof(double *)=4 sizeof(struct StudentInfo *)=4 sizeof(struct StudentInfo2 *)=4 Press any key to continue */ /*64位Ubuntu执行结果: sizeof(char)=1, sizeof(short)=2, sizeof(int)=4, sizeof(long)=8 sizeof(float)=4, sizeof(double)=8, sizeof(buf)=100 sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12 sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12 sizeof(int *)=8 sizeof(char *)=8 sizeof(short *)=8 sizeof(long *)=8 sizeof(double *)=8 sizeof(struct StudentInfo *)=8 sizeof(struct StudentInfo2 *)=8 */ 证明: 1.char/short/int/long/double/struct xx等所有变量类型指针,在32位系统下是4字节,在64位系统下都是8字节。 2.64位与32位linux c开发时默认字节对齐方式分别为8和4。 问题:对于结构体struct StudentInfo2,有时候会产生内存大小为16的结果,暂不知道原因。
1.3提问
提问:老师是不是只读的变量放在flash中?所以只读的加个const就节省ram?
答:应该是只读的常量(?),保存在flash中,但有时可能会被优化而放到内存中。
提问:老师,我要测试指针p占得内存使用sizeof(p)还是sizeof(*p)
2.变量赋值
2.1 简单的变量赋值
int a; a = 1; a = 'A';
2.2 sizeof和关键字
关键:关键字的关键就是作用域。
2.2.1 sizeof
-
sizeof()与结构体(来源:C和指针>结构的存储分配)
sizeof操作符能够得出一个结构的整体长度,包括因边界对齐而跳过的那些字节。如果你必须确定结构某个成员的实际位置,应该考虑边界对齐因素,可以使用offsetof宏(定义于stddef.h)。 offsetof( type,member ) type就是结构的类型,member就是你需要的那个成员名。表达式的结果是一个size_t值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。例如,对前面那个声明而言, offsetof( struct ALIGN, b ) 的返回值是4。
2.2.2 关键字
volatile — 作用/使用场景
例程
int main(void){ volatile int i; for(i=0; i<100; i++){...} ... }
演示:
解析:对于for()循环i++操作,对于变量i的自加操作将直接在cpu的内部寄存器中执行,提高效率;使用volatile修饰后,i++操作讲先从ram读出i的值而后再计算并返还结果到ram。这个操作对于普通变量并无意义,但对于寄存器则是必须的。
使用情景1:
volatile int *p; //未使用的全局变量,volatile防止编译器因为变量未使用而自动优化去掉;
使用情景2:
#ifndef S3C2440_SOC_H #define S3C2440_SOC_H
#define REG(x) (*(volatile unsigned int *)(x)) #define REG_BYTE(x) (*(volatile unsigned char *)(x))
/Memory Controllers/ #define BWSCON REG(0x48000000) //Bus width & wait status control #define BANKCON0 REG(0x48000004) //Boot ROM control
关键字作用原理:
(1) int i = 1; i++;
i++时,编译器可能会自动保存/备份变量到CPU寄存器中,i++修改时,将在CPU中直接执行,提高执行效率。
(2)volatile int i =1; i++;
i++时,将重新从RAM中读出i,然后再CPU中i++不进行优化
注:硬件寄存器必须用volatile。
- 个人理解:
①对于非寄存器的变量,都是在ram中存储,而变量的操作是:
从ram读出到cpu通用寄存器->在cpu作数学运算并得到结果->保存结果到ram
这类变量的操作都必须通过cpu的通用寄存器来产生结果,因此,这类变量在cpu直接执行,不会产生错误。
②对于硬件寄存器中的输入GPIO/状态类寄存器,会根据外部电信号而自动变更状态(不经过cpu),因此这类寄存器(对于CPU来说寄存器和ram的变量一样,都是特定地址上的存储单元)不能通过CPU优化。
const
源码:
volatile const int d_cont = 0x12345678; volatile const int d_const_array[10] = {1,2,3,}; int main(void) {}
结论:暂没证明const常变量保存在flash区,可能KEIL工具的原因。const修饰的只读的变量放在flash中,节省ram本身是正确的。
static
提问:老师,关于主题static的问题:a.c中定义static int add( ),并在(本文件)上面定义的函数还没生效时,调用add( )函数(其他文件,如b.c中也定义了全局的add()函数),是不是使用的是外部的全局函数?
答:韦老师代码有问题,本地测试会出错,测试未完成。
extern
摘要:外部(文件)变量的几种引用方式&建议方式、个人总结笔记
b.c: int b; a.c: extern int b;
对于外部(文件)变量的引用方法(例:b.c中定义变量int b并在a.c中引用):
-
可以直接在a.c中使用extern声明引用其他文件的全局变量:extern int b, 即可完成变量b的引用;
-
声明外部变量到某个“b.h”中并include(建议使用,减少耦合利于团队分工合作);
-
通过函数,得到b.c文件中的变量b的值并声明函数到b.h:int get_b();
-
变量使用,一般最好加上static;
注:不建议用全局变量,包括上面方式1/2,若想使用可以使用方式3,在不暴露变量的情况下使用函数包裹。
补:什么是回调函数和钩子函数?
提问1:外部定义的extern变量,需要怎么做才能引用?是需要include吗? 提问2:在外部的文件里定义的的变量声明为extern,编译器怎么能找的到?是不是一定要和引用文件同一级目录?如果子目录是不是就找不到了? 还是不太理解这个extern的意义。 直接引用头文件中的全局变量也不会有错误啊,extern感觉没有特别的意义啊。
提问3:extern的关键字应该由提供方添加还是应该由适用方添加?
提问4:老师,还有一个问题,就是编译器怎么知道去哪个路径里寻找extern的符号?是Makefile指定,还是默认当前路径?
答1:看上面总结。
答2:(1)a.c/b.c...编译时,会被编译成不同的a.o/b.o/...,然后所有xx.o文件将被链接成同一个xx.hex/xx.bin文件(所谓链接就是确定一下它们的地址)。(2)不需要同一目录。(3)头文件放全局变量会有问题,若如此操作,该头文件被多个文件包含并编译链接时,会造成重复定义。
答3:看上面总结。
答4:不找。
我的笔记:
1.对于对于外部(文件)变量的引用方法3的意义思考:使用函数封装变量,只传递变量值到使用方,可以避免使用方的某些不规范操作(如:内存越界或取址等)修改了变量的值。
2.3 struct引用结构体变量&结构体的赋值
摘要:结构体类型/变量的内存分配、数据结构(结构体变量的首地址/长度,成员变量的首地址、长度、填充字节数、内存分配是否连续)
2.3.1 引入结构体
/* Standard includes. */ #include <stdio.h> /* 打印同学的姓名、年龄 */ struct person { char *name; int age; }; struct person2 { char name[100]; int age; }; struct person3 { char sex; int age; }; struct person4 { char sex; char high; int age; }; struct company { char *name; struct person worker[100]; }; int main(void) { struct person wei = {"weidongshan", 40}; printf("sizeof(struct person) = %d, sizeof(struct person2) = %d\r\n", \ sizeof(struct person), sizeof(struct person2)); printf("sizeof(struct person *) = %d, sizeof(struct person2 *) = %d\r\n", \ sizeof(struct person *), sizeof(struct person2 *)); printf("sizeof(struct person3) = %d, sizeof(struct person3 *) = %d\r\n", \ sizeof(struct person3), sizeof(struct person3 *)); printf("sizeof(struct person4) = %d, sizeof(struct person4 *) = %d\r\n", \ sizeof(struct person4), sizeof(struct person4 *)); /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */ while (1); return 0; }
效率,并且有些硬件并不支持,例如使用奇数访问单个地址是合格的,但是作为基地址访问4字节内存就不合格。
2.3.2 问题
1.结构体类型占内存吗?
答:数据类型不占内存空间,变量才占。进行实例化/变量后,才占内存空间。
2.典型结构体案例
#include <stdio.h> struct StudentInfo{ //占12字节 char *name; int age; int high; }; struct StudentInfo2{ //占12字节 char num; int age; int high; }; struct StudentInfo3{ //占20字节 char name[10]; int age; int high; }; int main(void) { printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d, sizeof(struct StudentInfo3)=%d\n", sizeof(struct StudentInfo), sizeof(struct StudentInfo2), sizeof(struct StudentInfo3)); ... } /* Microsoft Visual C++6.0测试结果,32位: sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12, sizeof(struct Stu dentInfo3)=20(四字节对齐,数组name[10]+未使用部分=分配12字节)
3.结构体数据类型的元素内存分配问题
问题1:如该struct person4多字符元素结构体数据类型所占内存大小是否是6字节还是8/12字节?
问题2:对于由字符指针和数组元素组成的结构体变量,其内存空间大小是如何计算的?
答1:8字节。
对于结构体多字符类元素的内存使用情况如下:
答2:
(2)结构体变量赋值
总结
百科:Win32平台下的微软C编译器(cl.exefor 80×86)的对齐策略: 1)结构体变量的首地址是其最长基本类型成员的整数倍; 2)结构体每个成员相对于结构体首地址的偏移量(offset)都是(该)成员大小的整数倍,如有需要编译器会在成员之间加上填充字节; 3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节; 4)结构体内类型相同的连续元素将在连续的空间内,和数组一样。
C和指针: 系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的数据类型所要求的位置。
链接:[结构体的内存分配机制](https://www.cnblogs.com/qiumingcheng/p/11297988.html)
个人总结: 1.(32位)系统对于内存的分配和使用始终遵循4字节对齐,对于结构体变量的整体内存/元素内存分配仍遵循4字节(32位系统)/8字节(64位系统)对齐分配; 2.对于一次分配的4字节单位内存,若有相邻char/short等少于4字节的数据类型元素,将叠加处理; 3.使用sizeof查看的结构体变量内存,将是其实际占用的内存大小,且有可能小于4字节大小。
... struct StudentInfo5{ char num; char age2; int age; }; struct StudentInfo6{ char num; char age2; }; struct StudentInfo7{ short high2; short high3; }; int main(void) { printf("sizeof(struct StudentInfo5)=%d, sizeof(struct StudentInfo6)=%d, sizeof(struct StudentInfo7)=%d\n", sizeof(struct StudentInfo5), sizeof(struct StudentInfo6), \ sizeof(struct StudentInfo7)); return 0; } /*Microsoft Visual C++6.0执行结果: sizeof(struct StudentInfo5)=8,sizeof(struct StudentInfo6)=2, sizeof(struct StudentInfo7)=4 Press any key to continue */
4.探究数组成员对于结构体的意义
数组name[10]是一个元素,还是其每个数组元素都是一个结构体成员?
单从成员调用情况看,两种描述方式都正确,但从结构体类型成员内存分配机制的规则来看,应该数组的每个元素都被结构体当做一个结构体成员处理。
例程: #include <stdio.h> struct StudentInfo13{ //探究数组成员对于结构体的意义; char name[10]; int high4; }; struct StudentInfo14{ char name[10]; char high4; }; int main(void) { printf("sizeof(struct StudentInfo13)=%d\n", sizeof(struct StudentInfo13)); printf("sizeof(struct StudentInfo14)=%d\n", sizeof(struct StudentInfo14)); struct StudentInfo13 Huxiaohu= {"HuXiaohu", 174}; printf("Huxiaohu.name = %s, Huxiaohu.high4=%d\n", Huxiaohu.name, Huxiaohu.high4); printf("Huxiaohu.name = %c, Huxiaohu.high4=%d\n", Huxiaohu.name[0], Huxiaohu.high4); return 0; } /* sizeof(structStudentInfo13)=16 sizeof(struct StudentInfo14)=11 Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174 Huxiaohu.name = H, Huxiaohu.high4=174 Press any key to continue */
我的测试:
#include <stdio.h> struct StudentInfo{ char *name; int age; int high; }; struct StudentInfo2{ char num; int age; int high; }; struct StudentInfo3{ char name[10]; int age; //int high; }; struct StudentInfo4{ char num; char age2; char age3; int age; int high; }; struct StudentInfo5{ char num; char age2; int age; }; struct StudentInfo6{ char num; char age2; }; struct StudentInfo7{ short high2; short high3; }; struct StudentInfo8{ char high2; short high3; }; struct StudentInfo9{ int high2; short high3; }; struct StudentInfo10{ short high3; char high2; }; struct StudentInfo11{ double high3; char high2; }; struct StudentInfo12{ char high2; char high4; double high3; }; struct StudentInfo13{ //探究数组成员对于结构体的意义; char name[10]; int high4; /*数组name[10]是一个元素,还是其每个数组元素都是一个结构体元素?*/ }; struct StudentInfo14{ char name[10]; char high4; }; int main(void) { printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d, sizeof(struct StudentInfo3)=%d, sizeof(struct StudentInfo4)=%d\n", sizeof(struct StudentInfo), sizeof(struct StudentInfo2), sizeof(struct StudentInfo3),sizeof(struct StudentInfo4)); printf("sizeof(struct StudentInfo5)=%d, sizeof(struct StudentInfo6)=%d, sizeof(struct StudentInfo7)=%d\n", sizeof(struct StudentInfo5), sizeof(struct StudentInfo6), sizeof(struct StudentInfo7)); printf("sizeof(struct StudentInfo8)=%d, sizeof(struct StudentInfo9)=%d, sizeof(struct StudentInfo10)=%d\n", sizeof(struct StudentInfo8), sizeof(struct StudentInfo9), sizeof(struct StudentInfo10)); printf("sizeof(struct StudentInfo11)=%d, sizeof(struct StudentInfo12)=%d, sizeof(struct StudentInfo13)=%d\n", sizeof(struct StudentInfo11), sizeof(struct StudentInfo12), sizeof(struct StudentInfo13)); printf("sizeof(struct StudentInfo14)=%d\n", sizeof(struct StudentInfo14)); struct StudentInfo13 Huxiaohu= {"HuXiaohu", 174}; printf("Huxiaohu.name = %s, Huxiaohu.high4=%d\n", Huxiaohu.name, Huxiaohu.high4); printf("Huxiaohu.name = %c, Huxiaohu.high4=%d\n", Huxiaohu.name[0], Huxiaohu.high4); return 0; } /*C++6.0结果: sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12, sizeof(struct StudentInfo3)=16, sizeof(struct StudentInfo4)=12 sizeof(struct StudentInfo5)=8, sizeof(struct StudentInfo6)=2, sizeof(struct StudentInfo7)=4 sizeof(struct StudentInfo8)=4, sizeof(struct StudentInfo9)=8, sizeof(struct StudentInfo10)=4 sizeof(struct StudentInfo11)=16, sizeof(struct StudentInfo12)=16, sizeof(structStudentInfo13)=16 sizeof(struct StudentInfo14)=11 Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174 Huxiaohu.name = H, Huxiaohu.high4=174 Press any key to continue 64位Ubuntu执行结果: sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12, sizeof(struct StudentInfo3)=16, sizeof(struct StudentInfo4)=12 sizeof(struct StudentInfo5)=8, sizeof(struct StudentInfo6)=2, sizeof(struct StudentInfo7)=4 sizeof(struct StudentInfo8)=4, sizeof(struct StudentInfo9)=8, sizeof(struct StudentInfo10)=4 sizeof(struct StudentInfo11)=16, sizeof(struct StudentInfo12)=16, sizeof(struct StudentInfo13)=16 sizeof(struct StudentInfo14)=11 Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174 Huxiaohu.name = H, Huxiaohu.high4=174 */
2.3.3 结构体赋值
注:变量修改值时写几个字节(到该变量的内存空间),取决于变量的类型。
/*目的:探讨变量内存越界操作的典型案例及防范*/ #include <stdio.h> int main(void) { char c_val = 'A'; char c_val2 = 'B'; int i_val = 0x12345678; int *p; printf("&c_val = %p, &c_val2 = %p, &i_val = %p\n", &c_val, &c_val2, &i_val); printf("c_val = 0x%c, c_val2 = 0x%c, i_val = 0x%x\n", c_val, c_val2, i_val); p = (int *)&c_val; *p = 'H'; printf("c_val = 0x%c, c_val2 = 0x%c, i_val = 0x%x\n", c_val, c_val2, i_val); return 0; } /* 在C++6.0系统运行结果: &c_val = 0018FF44, &c_val2 = 0018FF40, &i_val = 0018FF3C c_val = 0xA, c_val2 = 0xB, i_val = 0x12345678 c_val = 0xH, c_val2 = 0xB, i_val = 0x12345678 Press any key to continue 在64位linux系统运行结果: &c_val = 0x0x7fff8815958b, &i_val = 0x0x7fff8815958c c_val = 0x42, i_val = 0x12000000 &c_val = 0x7ffc339b194a, &c_val2 = 0x7ffc339b194b, &i_val = 0x7ffc339b194c c_val = 0xA, c_val2 = 0xB, i_val = 0x12345678 c_val = 0xH, c_val2 = 0x, i_val = 0x12340000 */
案例2: /*目的:探讨变量内存越界操作的典型案例及防范*/ #include <stdio.h> struct person { char c; char d; int age; }; struct person wei = {'A', 'B', 0x12345678}; int main( void ) { int *p; p = (int *)&wei.c; *p = 'C'; printf("wei.c = 0x%p, wei.d = 0x%p, wei.age = 0x%p\n", &wei.c, &wei.d, &wei.age); printf("wei.c = 0x%x, wei.d = 0x%x, wei.age = 0x%x\n", wei.c, wei.d, wei.age); printf("wei.c = %c, wei.d = %c, wei.age = 0x%x\n", wei.c, wei.d, wei.age); return 0; } /* wei.c =0x00424A30,wei.d =0x00424A31,wei.age = 0x00424A34 wei.c =0x43,wei.d =0x0,wei.age=0x12345678 wei.c= C,wei.d= ,wei.age=0x12345678 Press any key to continue, */
2.4 类型转换
int <=== char
struct <=== struct pointer
2.5 指针&函数指针
2.5.1 变量赋值的解析
(1)C 指令 int a=123解析
a.通过cpu写指令;
b.怎么知道/如何写123?
I. cpu读flash得到指令(该C指令可能包括好几条汇编指令);
II. cpu执行指令
III. 写内存变量a
a = 123; //隐含了地址的操作 等同于 int *p=&a; *p=123;
读/写数据到flash、sdram等存储单元的意义
个人理解:对于CPU来说,操作块flash、块sdram等存储器,以及变量,都属于同一类操作,都是对统一编址的存储单元的某部分进行读写。
2.5.2 通过指针赋值
如上。
2.5.3 结构体指针
(1)定义结构体类型时用结构体自身类型或指针定义内部成员的方法:
例程:定义一个包含学生信息结构体
正确示例:
struct student{
char *name;
int age;
struct student *pclassmate;
};
typedef struct Student{
char *name;
int age;
struct Student *classmate;
}Student_t, *Student_pt;
typedef struct Student{
struct Student Exp1; //结构体Student还没定义好,编译器不知道该类型所需多大空间,因此会报错
struct Student * pExp2; // pExp2是一个指针,OK
void fun1(struct Student Stud3); /*Stud3是一个结构体变量做函数参数:
(1)函数参数在运行时才会压栈,因此编译是没有问题的;
(2)运行时,结构体类型已经是定义好的,因此也不会有问题*/
void fun2(struct Student * pStud4); //pStud4是一个指针做函数参数,也是OK的
}Student_t, *Student_pt;
注:该例子在说明定义自身类型成员以及指针时,用的是struct Student,而不是Student_t。
错误示例:
struct student{
char *name;
int age;
struct student classmate; //结果:无限套娃了,结构体的大小递归编译器无法计算,应使用结构体指针。
};
typedef struct {
char *name;
int age;
struct Student *classmate;
}Student, *pt_Student;
总结:
定义自身类型的成员是不可以的 定义结构体时,是不可以定义自身类型的成员的。这是因为结构体中各个成员所需的存储空间大小是编译阶段确定的,当用该结构体定义自身成员时,由于结构体大小还不确定,因此此时定义的陈冠所需要的存储空间大小也就不确定,因此编译会报错; 定义自身类型的指针成员是可以的 但是可以定义该结构体类型的指针,因为指针在固定的平台上所占的内存大小是确定的! 定义成员函数时,结构体类型做函数参数类型是可以的 函数参数在运行时才会压栈,因此编译是没有问题的;运行时,结构体类型已经是定义好的,因此也不会有问题。 定义成员函数时,结构体指针做函数参数类型是可以的 指针做函数参数,没有问题; 原文链接:定义结构体类型时用结构体自身类型或指针定义内部成员_mengshushu90的博客-CSDN博客
结构体变量&结构体指针作为函数参数
结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。
2.5.4 函数指针
摘要:函数指针的本质,函数指针变量的定义方式、强制类型转换、使用方法,公司使用案例(如何使程序支持(并自动识别)多款(同类型,可相互替代)设备)
(1)函数指针也是地址
《C程序设计语言》5.11 指向函数的指针: 在C语言中,函数本身不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。
个人理解: 函数也是有地址的,有地址就可以被指针变量指向。
函数指针变量的定义方式:
变量指针:int *p; 函数指针:void (*play_ball)(void);//定义一个(函数指针)变量(它不再是一个函数),在内存里必定有一块内存来保存它;
(2)我的测试 & 强制类型转换 & 使用方法
#include <stdio.h>
void (*Specialty_p)(int a, int b);
void add(int a, int b)
{ }
int main(void)
{
int val = 10;
int *p = &val;
Specialty_p = add;
printf("&val = 0x%p, Specialty_p = 0x%p \n", p, Specialty_p); //&val = 0x0018FF44, Specialty_p = 0x0040100F;
Specialty_p = (void (*)(int, int))p;
printf("&val = 0x%p, Specialty_p = 0x%p \n", p, Specialty_p); //&val = 0x0018FF44, Specialty_p = 0x0018FF44;
printf("func pointer len = %d\n", sizeof(Specialty_p)); //结果:func pointer len = 4;
return 0;
}
总结:
1.函数指针大小4字节;
2.函数指针是一种指向特定类型函数的指针,同结构体类型指针类似且同等级。
3.强制类型转换的方法:类似(void (*)(int, int)),去掉指针名字(*后面位置)和形参名字,保留结构;
4.函数名的本身或者取地址,都是同一个函数的地址,特权;
5.函数指针和函数的用法完全一样。
#include <stdio.h>
typedef struct Student{
char *name;
int age;
struct Student *classmate;
void (*Specialty_p)(void);
}Student_t, *pt_Student;
void (*Specialty_p)(void);
static void play_ball(void){
printf("play ball \n");
}
static void sing_song(void){
printf("sing_song \n");
}
int main(void)
{
Student_t Stu[2] = {
{"ZhangSan", 31, NULL, play_ball},
{"LiSi", 28, NULL, sing_song},
};
Stu[0].classmate = &Stu[1];
Stu[1].classmate = &Stu[0];
int i;
for(i = 0; i < 2; i++)
{
printf("%s's classmate is: %s, specialty is: ", Stu[i].name, Stu[i].classmate->name);
Stu[i].Specialty_p();
}
/*
ZhangSan's classmate is: LiSi, specialty is: play ball
LiSi's classmate is: ZhangSan, specialty is: sing_song
Press any key to continue
*/
}
(3)案例-如何使程序支持(并自动识别)多款设备
公司某产品有多款LCD屏幕(LCD_A、LCD_B...),如何使程序支持(并自动识别)多款设备。
方式1:通过宏判断; //#define LCD_TYPE A int main(void){ #ifdef LCD_TYPE_A draw_logo_lcda(); #else draw_logo_lcdb(); #endif while (1); return 0; } //缺点:每换一种屏幕,都需要修改代码,至少需要修改宏LCD_TYPE,重新编译重新烧写。
方式2:假若LCD屏幕自带EEROM,内含设备号等信息,则可通过读取type来自动判断识别设备; //根据硬件的信息,动态的判断调用各种函数。 int read_eeprom(void){ //对于真实的产品,应该读flash,读EEROM /* 0:lcd a; 1:lcd b */ return 0; } int get_lcd_type(void){ return read_eeprom (); } int main(void){ int type = get_lcd_type(); if(type == 0) draw_logo_lcda(); else if(type == 1) draw_logo_lcdb(); while (1); return 0; } //缺点:当公司产品线较多,有很多LCD屏幕时,则需要做很多判断,代码冗余。
方式3:在方式2基础上优化 typedef struct Lcd_operation{ int type; void (*draw_logo)(void); }Lcd_operation_t, *Lcd_operation_pt; static Lcd_operation_t xx_lcds[] = { {0, draw_logo_lcda()}, {1, draw_logo_lcdb()} }; int read_eeprom(void){ //对于真实的产品,应该读flash,读EEROM /* 0:lcd a; 1:lcd b */ return 0; } int get_lcd_type(void){ return read_eeprom (); } Lcd_operation_pt get_lcd(void){ int type = get_lcd_type(); return &xx_lcds[type]; //1.为什么加&?加或不加&都正确,但&则开销更小效率更高,返回地址只传输4字节,而传输整个结构体则需要传输8字节。 2.尽量避免使用全局变量(如:xx_lcds),若想使用,用函数封装起来。好处:获得硬件类型的函数修改了,只需要来这里修改下就好(分离分层?各层模块化修改最小?) } int main(void){ Lcd_operation_pt lcd_present = get_lcd(); lcd_present->draw_logo(); while (1); return 0; } //代码变得很漂亮了, /*C语言声明/定义数组变量时,可不指定数组大小的情况:(只有以下三种情况) (1)数组是形式参数 (2)数组声明的同时进行了初始化 (3)数组的存储类型为extern 参考自:《C语言程序设计:现代方法》P334 */
问题:
提问1:结构体为什么可以节省硬件资源?
答:使用结构体可以很方便的编写程序,且当程序设计的好的话,可以节省一部分硬件资源(如上面3相较于2节省了判断,从而节省了资源)。
提问2:老师,曾经看到过,在一个结构体初始化中这样写{read=xx,.wite=xx},这个.read和.write是指明属性赋值吗?
提问3:老师,结构体的点初始化是不是把C99那个勾上就行了啊?(C语言在1989年出了一个C89标准,1999年出了一个C99标准,2011年出了一个C11标准(最新的))
lcd_operation lcd_a_ops = { .type =0, .draw_logo = draw_logo_lcda, }; //答2/3:这是C99的扩展用法(KEIL需要单独勾选),可以只初始化用到的结构体成员,更方便初始化。
提问4:typedef定义结构体变量的别名能不能好好讲讲,有时候理不清楚
typedef int (*add_type) (int, int); //typedef函数指针类型重定义; add_type add1, add2; //定义两个int (*) (int, int)类型函数指针;
3.链表操作
3.1 基础链表
//案例:单线间谍组织名单输出; typedef struct spy { char *name; struct spy *next; }spy, *p_spy; spy A = {"A", NULL}; spy B = {"B", NULL}; spy C = {"C", NULL}; int main( void ){ p_spy head = NULL; A.next = &B; B.next = &C; C.next = NULL; head = &A; while (head) { printf("%s\r\n", head->name); head = head->next; } return 0; }
课堂图:
3.2 链表插入
3.3 链表删除
//在链表末尾插入新节点; typedef struct spy { char *name; struct spy *next; }spy, *p_spy; spy A = {"A", NULL}; spy B = {"B", NULL}; spy C = {"C", NULL}; spy D = {"D", NULL}; p_spy head = NULL; void insert_spy(p_spy newspy) { p_spy last; if (head == NULL) { head = newspy; newspy->next = NULL; } else { /* 先找到链表的最后一项 last */ last = head; while (last) { if (last->next == NULL) /* 找到了 */ break; else last = last->next; } last->next = newspy; newspy->next = NULL; } } void remove_spy(p_spy oldspy) { p_spy left; if (head == oldspy) { head = oldspy->next; } else { /* 找出oldspy的上线 */ left = head; while (left) { if (left->next == oldspy) break; else left = left->next; } if (left) { left->next = oldspy->next; } } } void print_spy(void) { p_spy tmp = head; while (tmp) { printf("%s\r\n", tmp->name); tmp = tmp->next; } } int main( void ) { insert_spy(&A); insert_spy(&B); insert_spy(&C); insert_spy(&D); print_spy(); remove_spy(&B); printf("remove spy B: \r\n"); print_spy(); while (1); return 0; }
4.ARM架构
flash只会存储汇编指令,一条基础汇编指令4字节。ARM架构是精简指令集,对内存来说,只有读/写两个操作,所有的计算都是在cpu内部实现的。
例程: int main( void ){ volatile int a = 1; volatile int b = 2; a = a + b; return 0; }
5.几条汇编指令(堆、栈)
-
load、store
-
add、sub
-
and/bic
-
B、BL(Branch)
一个程序运行的本质是什么,全局变量是怎么被设置的,局部变量是在哪里,需要看一个完整程序的反汇编
6. 几个核心问题
6.1 有值的全局变量的初始化
全局变量的初值来自flash,其初始化方式分两种:
(1)对所有全局变量依次单个赋值初始化
LDR R0, =0X12345678 //伪指令,等同于:MOV R0, #0X12345678 即: LDR R1, [Flash_addr] STR R1, [g_xx_ram_addr]
缺点:当数据量很大时,效率很低。
(2)类似memcpy,把Flash上的数据段,整体拷贝到RAM(系统实际使用的方式)
6.2 全局变量的在内存/flash掉电特点:
内存掉电丢失,flash掉电不丢失。
6.3 初始值为0、没有初始化的全局变量,怎么初始化?
-
有100万个这样的变量
-
这100万个变量,都保存在Flash上吗?不。
-
这些变量在内存里都放在ZI段(即.bss段)
-
类似memset,把ZI段全部清零
6.4 (完成前面之后)才去调用main函数
6.5 局部变量在哪?
在栈里。
6.6 局部变量的初始化
(1)局部变量的初值在.text代码中;
(2)初始化或赋值,需要一个一个的写;
6.7 栈的作用
(1)函数嵌套使用时,LR寄存器(保存函数的返回地址)会被覆盖,因此,进入函数后,需要保存LR寄存器到栈中;
(2)保存R0~R3寄存器中的值;
int g_a3 = 0; int main(void) { volatile int a = l; volatile int b = 2; a = a+b;
提问1:
提问2: