#define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) {UART_BYTE = byte;// write byte to 0x40008000 addressUART_SEND;// set bit number 4 of address 0x40008001 }
send_uart函数的第一行代码可扩展为:
【C语言名称怎么来的?有A和B语言吗?没A语言有B语言,正是C的源头】
*(char *)0x40008000 = byte;
这一行代码是告诉编译器将值是0x40008000解释为一个指向char的指针,然后解除对该指针的定义(给出该指针所指向的值)(用最左边的*操作符),最后将字节值分配给该解除定义的指针 。换句话说:把变量byte的值写到内存地址0x40008000 。
将该函数的下一行代码扩展一下:
*(volatile char *)0x40008001 |= 0x08;
在这行代码中,我们对地址0x40008001和数值0x08(二进制的00001000,即第4位的1)进行了or位运算操作,并将结果存回地址0x40008001 。换句话说:我们设置地址为0x40008001的字节的第4位 。我们还声明地址为0x40008001的值是易失性的 。这就告诉编译器,该值可能会被我们代码外部的进程所修改,所以编译器在写入该地址后不会对该地址的值做出任何假设 。(在这种情况下,该字节在我们用软件设置后就被UART硬件取消了) 。这些信息对于编译器的优化器来说是很重要的 。例如,如果我们在for循环中这样做,而没有指定该值是易失性的,编译器可能会认为该值在被设置后永远不会改变,并在第一个循环后跳过执行该命令 。
确定资源使用开发人员进行系统编程不能依赖的一个常见语言特性就是垃圾收集,甚至对一些嵌入式系统来说,只能进行动态分配 。嵌入式应用程序在时间和内存资源方面非常有限 。对于一些实时的嵌入系统,它们无法承受垃圾收集器的非确定性调用 。如果因为内存不足而不能使用动态分配,那么拥有其他内存管理机制就显得尤为重要,比如将数据放在自定义地址中,就像C语言的指针所允许的那样 。那些严重依赖动态分配和垃圾回收的语言不适用于资源紧张的系统 。
Code SizeC语言有一个非常小的运行时,其代码的内存占用要小于其它语言 。例如与C++相比,一个由C语言生成的二进制文件,其体积大约是由类似的C++代码生成的二进制文件的一半 。造成这种情况的主要原因之一是异常支持 。
异常(Exceptions )机制是C++比C语言多出来的一个不错功能,如果异常不被触发和巧妙的实现,他们实际上是没有执行时间的开销,但代价便是增加代码体积 。
下面让我们以C++代码为例:
// Class A declaration. Methods defined somewhere else; class A{public:A();// Constructor~A();// Destructor (called when the object goes out of scope or is deleted)void myMethod();// Just a method};// Class B declaration. Methods defined somewhere else;class B{public:B();// Constructor~B();// Destructorvoid myMethod();// Just a method};// Class C declaration. Methods defined somewhere else;class C{public:C();// Constructor~C();// Destructorvoid myMethod();// Just a method};void myFunction(){A a;// Constructor a.A() called. (Checkpoint 1){B b;// Constructor b.B() called. (Checkpoint 2)b.myMethod();//(Checkpoint 3)}// b.~B() destructor called. (Checkpoint 4){C c;// Constructor c.C() called. (Checkpoint 5)c.myMethod();//(Checkpoint 6)}// c.~C() destructor called. (Checkpoint 7)a.myMethod();//(Checkpoint 8)}// a.~A() destructor called. (Checkpoint 9)
该段代码中的A类、B类和C类中的方法都被定义在了外部(例如在其它文件中) 。因此,编译器无法对它们进行解析,也不知道是否会抛出异常 。所以程序必须准备处理从它们的任何构造函数、析构函数或其他方法调用中抛出的异常 。解构器不应该抛出(做法非常糟糕),但用户还是可以抛出,或者他们可以通过调用一些抛出异常的函数或方法(显式或隐式)间接地抛出 。
如果myFunction中的任何调用抛出了异常,堆栈解开机制必须能够调用所有已经构建的对象的析构器 。堆栈解开机制的一个实现将使用这个函数的最后一次调用的返回地址来验证触发异常的调用的 "检查点编号"(这是简单的解释) 。它是通过利用一个辅助的自动生成的函数(一种查找表)来实现的,当该函数的主体抛出异常时,该函数将被用于堆栈解绕,这将与此类似 。
如果myFunction函数的任何一个调用抛出异常,C++的栈展开(stack unwinding)机制必须能够调用所有已构建对象的析构器 。栈展开机制的一个实现是将使用这个函数的最后一次调用的返回地址来验证触发异常调用的 "检查点编号"(这是简单的解释) 。它是通过利用一个辅助的自动生成函数(一种查找表)来实现,在该函数的主体抛出异常时,该函数将被用于堆栈解绕,与下面这段代码类似:
推荐阅读
- 一门备受争议却又曾风靡学术界的编程语言
- Win10系统分了几个版本?怎么选择?哪个更好用?
- Anaconda环境及Python语言的下载与安装方法
- vlog是什么意思?怎么读?
- 眼肌无力是怎么引起的
- 脑梗语言康复训练方法
- 老人干咳嗽老不好怎么办
- 老人呕吐不止怎么办
- 老人家打嗝是怎么回事
- 老年人体温晚上高是怎么回事?