Presentation is loading. Please wait.

Presentation is loading. Please wait.

单片机原理与应用 C/C++在现代数字计算机上的实现.

Similar presentations


Presentation on theme: "单片机原理与应用 C/C++在现代数字计算机上的实现."— Presentation transcript:

1 单片机原理与应用 C/C++在现代数字计算机上的实现

2 课程项目 安装并配置51单片机的软件开发环境 Code::Blocks+SDCC
实验报告模板有实验所需文件及详细实验步骤。从课程主页下载并逐步填写并提交。 严禁抄袭 提交截止: :59

3 C/C++的实现 C/C++在现代数字计算机上的实现 各种语法映射到实际硬件实现 高效地实现语言的各项特性 提供一定的资源管理能力
提供方便开发的工具、库

4 平坦地址空间模型 所有数据、代码共享一个地址空间 便于操作系统管理和分配内存 程序需要存储哪些信息? 如何实现这些功能和信息? 函数 数据
各个函数的指令 数据 全局变量,静态变量,局部(自动)变量 动态分配的存储空间,函数调用信息 如何实现这些功能和信息? 必须要清楚各自的功能和特点

5 局部(自动)变量 仅在定义变量的函数被调用时存在 当函数被递归调用时,可有多个不同变量 void main() { pow(0.5,3);
} 局部(自动)变量 仅在定义变量的函数被调用时存在 当函数被递归调用时,可有多个不同变量 float pow(float x, int n) { if(n==1) return x; if(n==0) return 1; int t = n/2; return pow(x,t)*pow(x,n-t); } main() x=?? n=?? t=?? pow(0.5,3) x=0.5 n=3 t=1 x=0.5 n=1 t=?? x=0.5 n=2 t=1 pow(0.5,1) pow(0.5,2) pow(0.5,1)

6 同步地把Root换成this(多线程安全)
volatile: 如一个变量可能被多个线程修改,应定义为volatile this: C++成员函数中指向当前对象的指针 全局变量 程序的整个生存周期都存在 常量 全局资源的管理数据 const char *FlagToName[]= { “create”, “open”, “append” }; 同步地把Root换成this(多线程安全)

7 静态变量 程序的整个生存周期内存在 当函数被递归调用时,也仅有一个变量 大多数静态变量可以用类变量代替 void main() {
pow(0.5,3); } 静态变量 程序的整个生存周期内存在 当函数被递归调用时,也仅有一个变量 float pow(float x, int n) { static int entryCount=0; entryCount ++; …… main() entryCount=0 pow(0.5,3) entryCount=1 entryCount=2 entryCount=3 pow(0.5,1) pow(0.5,2) 大多数静态变量可以用类变量代替 pow(0.5,1)

8 程序的其它信息 动态分配的存储空间 函数调用信息(返回地址等) 函数 生存周期由程序员控制 与局部变量相同 程序的整个生存周期内存在
可能跨越函数,也可能仅跨越几条语句 函数调用信息(返回地址等) 与局部变量相同 函数 程序的整个生存周期内存在 与各种数据都很不相同 与常量有一定相似性

9 程序的各种信息总结 程序的整个生存周期内存在 仅在定义变量的函数被调用时存在 由程序员控制 各部分应分别管理,放置在地址空间的不同部位
函数,全局变量,静态变量 仅在定义变量的函数被调用时存在 局部变量,函数调用信息 由程序员控制 动态分配的存储空间 各部分应分别管理,放置在地址空间的不同部位

10 程序在内存中的映象 全局和静态数据的实现没有区别 防止空指针错误 00000000 未映射空间 文件头 函数 函数1 数据 开始执行位置
__startup 函数在内存的顺序并不重要 call _main main() call _exit 函数n 操作系统的 代码、数据 FFFFFFFF

11 栈 管理函数调用和返回,分配局部变量 为什么用栈? 栈指针 栈框架 SP 函数可以递归调用 指向栈顶部的指针
未分配空间 SP printf() SF 管理函数调用和返回,分配局部变量 为什么用栈? 函数可以递归调用 栈指针 指向栈顶部的指针 栈框架 一个函数的调用信息、局部变量等所有的信息 Go() SF Test() SF main() SF 已分配空间 不能返回指向局部变量的指针或引用!

12 栈 SP SP ptr y (3) printf SP main main SA int * ptr ()
{ int y=3; return &y; }; main () { int *SA,content; SA = ptr(); content = *SA; printf("%d", content); /*??*/ content = *SA; printf("%d", content); /*??*/ }; SP SP ptr y (3) printf SP main SA(??) main SA main SA See 3 at stackAddress, its equal to 3

13 空间比较小:32位平台约1MB左右 如果函数调用太多,或者自动变量太多? Stack Overflow: 栈溢出

14 堆:程序员主动管理的内存 返回值:地址(?) 如何使用? 叫啥名? size new char[16*1024*1024];

15 指针(pointer) 存储地址的变量 char *photo = new char[16*1024*1024]; 堆 00230002
ffff0008 ffff000c ffff0010 ffff0014 photo

16 指针 指针存储的是地址 指针和整数的区别:没有! 一种情况是从堆分配的内存 合法的指针:(char*)12345
还有别的情况 指针和整数的区别:没有! 合法的指针:(char*)12345 指针的大小通常和整数的大小相同 32位平台,通常是32位,或4字节 与指针所指向的内容的大小无关

17 指针 指针是变量 指针的值可以被改变 变的是什么? char *photo=new char[16*1024*1024]; ……
也没变! 变了! 没变!

18 指针 访问指针指向的内容 指针可以如数组名一样使用 堆中的内存块就是数组 char *photo=new char[size];
for(int i=0;i<size;i++) photo[i] = 0; photo[eyePos] = …… faceColor = photo[facePos];

19 指针 易犯的错误 编译没有问题 typedef struct {…}TMyStruct;
TMyStruct *sptr = (TMyStruct *)malloc(n*sizeof(TMyStruct *)); C++ C 这个*是不能有的 struct TMyStruct{…}; TMyStruct *sptr = new TMyStruct *; 编译报错

20 指针 易犯的错误 假设变量大小 尽量用sizeof计算变量的大小
int *dynArray = (int*)malloc(mySize*4); 尽量用sizeof计算变量的大小 特别是结构的大小 结构的大小在不同的编译选项下面可能有差异

21 指针 指针的作用域 指针指向的内容呢? 如其它的变量 void ShowPhoto(const char *FileName) { ……
char *photo=new char[fileSize]; } 指针指向的内容呢?

22 指针 指针指向内容的作用域 由程序员控制 不再使用的时候,要释放 void ShowPhoto(const char *FileName)
{ …… char *photo=new char[fileSize]; delete []photo; }

23 指针 如果忘了释放? 还在! 内存泄漏(memory leak)!

24 指针 如果尚未分配内存就访问其指向的内容? 非法指针访问! char *photo; photo[facePos] = clPink; 
别的变量 非法指针访问!

25 指针 如果访问的单元超出了分配的范围? 指针越界! char *name=new char[5]; name[5] = 0;
Name[-1] = ‘L’; 指针越界!

26 指针 如何防止这些错误 小心? 开发工具? 良好的书写风格 人总是有犯错误的时候!
Borland C++ Builder 6以上版本可查这些错误 运行缓慢,需要很长时间调试 良好的书写风格 可以避免一部分错误

27 指针 良好的书写风格 总是把指针初始化成空 NULL 可以通过调试检测非法指针访问错误 空:通常定义为(void*)0
地址为0的内存单元一般被定义为不能被访问,如果有对这个单元的访问,则一定是错误 一般操作系统把最前和最后64KB地址都设为不能访问 可以通过调试检测非法指针访问错误 如何做? char *photo = NULL;

28 指针 空指针错误

29 指针 空指针错误 实验代码:NULL.7z(从课程主页下载)

30 指针 用C++类防止内存泄漏 构造函数:在定义变量的一开始被自动调用 析构函数:在变量被释放前被自动调用
均由编译器在适当的位置自动插入指令完成 不需要程序员管理 可以“忘掉”这些事情! 可以方便地管理内存分配与释放

31 指针 class TCharArray { public: char *img; int len; TCharArray() 要管理的内存
img = NULL; len = 0; } …… 要管理的内存 分配的内存大小 构造函数 初始化为空状态

32 指针 ~TCharArray() { 析构函数 Close(); } 调用内存释放函数(很重要!) void Close()
……//接上页 ~TCharArray() { Close(); } void Close() if(img!=NULL) delete []img; img = NULL; len = 0; …… 析构函数 调用内存释放函数(很重要!) 内存释放函数 测试是否已经分配 如是,则释放,并恢复成空状态

33 指针 void Alloc(int size) { Close(); img = new char[size]; len = size;
……//接上页 void Alloc(int size) { Close(); img = new char[size]; len = size; } …… }; 内存分配函数 调用内存释放函数(很重要!) 分配内存,设置成合法状态 实现其它功能

34 指针 如何使用? 定义变量,分配内存 如何访问其指向的内容? TCharArray photo;
faceColor = photo.img[facePos]; 书写很麻烦 希望:… = photo[facePos]; TCharArray photo; photo.Alloc(fileSize);

35 指针 操作符重载 重新定义操作符的含义 class TCharArray { …… char operator[](int inx)
return img[inx]; } }; 前面的内容 重载下标操作符[]

36 指针 指针越界? … = photo[-1]; … = photo[fileSize]; class TCharArray { ……
char operator[](int inx) return img[inx]; } }; if ( (inx<0) || (inx>=len) ) { ……//出错处理 }

37 指针 什么样的出错处理比较合适? 不知道!(程序逻辑上已经不可恢复) 用assert #include <_assert.h>
class TCharArray { …… char operator[](int inx) assert( (i>=0) && (i<len) ); return img[inx]; } }; 两处都需要!

38 指针 如何对其内容赋值? photo[facePos] = clPink; Compiler Error: LValue Required!
photo.img[facePos] = clPink; 麻烦!

39 指针 解决办法:返回引用 唯一的变化 char &operator[](int inx) {
…… char &operator[](int inx) { assert( (i>=0) && (i<len) ); return img[inx]; } }; TCharArray photo; photo.Alloc(fileSize); …… photo[facePos] = clPink; 

40 指针 返回引用和返回普通变量的区别 char &operator[](int inx) {
assert( (i>=0) && (i<len) ); return img[inx]; } char operator[](int inx) { assert( (i>=0) && (i<len) ); return img[inx]; } 再返回 直接返回该变量的访问方法 复制一份 char tmp=img[inx]

41 指针 多种数据类型,为每个类型写一个类? class TFloatArray class TIntArray
class TDoubleArray class TLongArray class TShortArray class TCharArray

42 模板 模板:管你是什么类型 TArray<int> IntArray;
template<class T> class TArray { public: T *Data; …… void Alloc(int n) { Close(); Data = new T[n];} T &operator[](int n) { return Data[n];} TArray<int> IntArray; TArray<float> FloatArray; TArray<TMyStruct> SA; TArray<TArray<int> > AA;

43 资源泄漏 用类机制防止其它资源泄漏 与上面过程类似 class TMyFile { public: FILE *MyFile; ……

44 函数 高级语言写的函数必须翻译成机器语言 需要翻译的内容 可执行文件中函数即是机器语言指令串 参数传递 函数调用 局部变量
函数内在功能(算法) 返回

45 函数 调用函数需要进行的操作 SP SP SP SP ret_add var1 fmt count …… 第一步:参数传递(先不管顺序)
void main() { int count = 10; printf(“%d times hello”,count); …… SP var1 SP fmt SP count …… 第一步:参数传递(先不管顺序) 第二步:记录返回值,转到函数入口

46 (ret_add到fmt之间的长度不定)
函数 调用函数需要进行的操作 SP i SP ret_add int printf(const char *fmt,...) { for(int i=0;fmt[i]!=0;i++) if(fmt[i]==‘%’) …… var1 fmt 从右到左传递参数 var1 fmt count …… 第三步:分配局部变量 函数如何知道fmt在哪里? (ret_add到fmt之间的长度不定)

47 函数 函数的参数传递 __cdecl, __stdcall: 从右到左顺序压栈 __pascal:从左到右顺序压栈
还有其它差别 __pascal:从左到右顺序压栈 __fastcall: 利用寄存器传递参数 速度稍快

48 函数 函数返回 SP SP SP i ret_add 第一步:清除局部变量 fmt 第二步:返回 var1 count 谁清除调用参数?
…… __cdecl, __pascal : 调用函数 __stdcall: 被调用函数 __fastcall: 基本不用清除

49 函数 函数的参数传递与清除 __cdecl: 从右到左,调用函数清除 __stdcall: 从右到左,被调用函数清除
可以方便地支持可变参数个数 在参数表末尾添加不必要的参数不会导致问题 __stdcall: 从右到左,被调用函数清除 __pascal: 从左到右,调用函数清除 __fastcall: 利用寄存器传递参数

50 函数 函数重载的实现 函数名转换 int OpenFile(const char *FileName) { ……
int OpenFile(const wchar_t *FileName) proc near …… proc near

51 函数 函数名转换 调用方式的区别 大小写 名修饰(name mangling, name decoration)
__cdecl, __stdcall, __fastcall:区分 __pascal:不区分 名修饰(name mangling, name decoration) C:不修饰(但可选择头上加下划线) extern “C” {…} C++:修饰(可以重载) extern “C++” {…}

52 函数 函数名转换 不同编译器的名修饰是不同的! 不同编译器编译的C++库不能通用 C++DLL不能被不同编译器编译的执行文件调用
编写DLL应使用extern “C” {…}以避免名修饰 名字前面的下划线? Ansi C/C++:缺省加 VC:缺省不加(且加一个尾巴!) 如果DLL链接出错,看看是否相应选项正确

53 函数 函数的地址和函数指针 CPU不知道名字 编译器必须把所有名字翻译成地址 ...... E8 ???? ......
void main() { int count = 10; printf(“%d times hello”,count); …… ...... …… call ???? E8 ???? ......

54 函数 函数的地址和函数指针 编译器不知道别的模块的名字对应的地址 把需要地址的地方记录下来 ...... E8 00000000
0x1234abcd ...... 0x1234abcd: _printf 引入符号表

55 函数 函数的地址和函数指针 编译器知道本模块的名字的地址 要把这些名字的地址告诉后续工具 main() Usage() push ebp
...... ret push ebp ... 0x ...... 0x ...... 0x : _main 0x : _Usage ...... 引出符号表

56 函数 函数的地址和函数指针 编译器的功能 但是 如何让程序执行起来? .c/.cpp ----> .obj
高级语言函数“翻译”成机器语言函数 但是 .obj不能执行 尚未解决的地址 如何让程序执行起来?

57 函数 函数的地址和函数指针 连接器 一个程序要调用多个函数 编译器生成多个独立的函数 连接器把函数合并到一起成为程序

58 函数 函数的地址和函数指针 连接器 0x00010000 ? __startup ? __startup 0x00001234: _main
0x : _exit

59 函数 函数的地址和函数指针 连接器 0x00010000  __startup  0x0001abcd _main ?
0x0001ef01 _exit _main 0x0000abcd: _printf _exit

60 函数 函数的地址和函数指针 连接器 0x00010000  __startup  0x0001abcd _main  _printf
…… 0x0001ef01 _exit _printf ? ……

61 函数 ?库文件一定叫.lib? 函数的地址和函数指针 经常使用的函数 库:收集经常使用的函数到一起 每次都重新编译? 放到哪里?
_printf _scanf ...... Dictionary _printf: _scanf: ...... ?库文件一定叫.lib?

62 函数 函数的地址和函数指针 生成程序的全过程 中间临时输出路径 .c .cpp .pas .f .asm 编译器 汇编器 .h .hpp
.inc ...... .obj .lib tlink.exe .exe tlib.exe 输出路径 库文件路径 源文件路径 头文件路径

63 函数 函数的地址和函数指针 函数地址 函数的执行入口在内存中的地址 函数指针 理论上:执行文件被加载到内存后才能确定函数地址
实际上:系统均有很强的约定,大多数函数地址可以在连接时确定 函数指针 指向函数的入口的指针 有什么用?

64 函数 函数指针 作用:调用所指向的函数 修改函数的代码? 获得函数的代码? 一般情况下不作这些用处 理论上可行 为什么用指针? 不用名?

65 函数 函数指针 当编译的时候无法确定函数名,则不能用名调用函数,只能用指针
例1:排序算法,编译时不能确定需要排序的数组的具体类型,就无法确定比较函数 例2:虚函数,编译时无法确定指向基类的指针的实际类型,就无法确定该用哪个函数 虚函数在编译器中是用函数指针实现的

66 函数 函数指针 定义和使用函数指针类型的语法 typedef int (*TCmp)(const void *,const void *);
可以不加名字 差异所在 基本上是函数的声明 函数指针可以如一般函数名一样使用 函数指针类型可以如一般变量类型使用 void qsort(void *base, size_t nelem, size_t width, TCmp cmp) {…… int cmpResult = cmp( (void*)(((int)base)+width*…);


Download ppt "单片机原理与应用 C/C++在现代数字计算机上的实现."

Similar presentations


Ads by Google