John Lee(1513562323) 20:32:26
上次讲到 baud_t。
John Lee(1513562323) 20:32:41
我们继续探索。
John Lee(1513562323) 20:33:16
union baud_t { // Baud Rate Divider Register (UA_BAUD)
__INLINE baud_t(uint32_t volatile& r) : ref(r), val(r), changed(0) { } \
__INLINE baud_t(uint32_t volatile& r, uint32_t v) : ref(r), val(v), changed((uint32_t)(-1)) { } \
__INLINE ~baud_t() { apply(); } \
__INLINE baud_t& apply() \
{ \
if (changed != 0) { \
ref = val; \
changed = 0; \
} \
return *this; \
} \
struct { \
volatile uint32_t& ref; \
uint32_t val; \
uint32_t changed; \
};
sfb_t<baud_t, uint32_t, 0, 16> BRD;
sfb_t<baud_t, uint32_t, 24, 4> DIVIDER_X;
sfb_t<baud_t, uint32_t, 28, 1> DIV_X_ONE;
sfb_t<baud_t, uint32_t, 29, 1> DIV_X_EN;
};
日期:2011/12/27
John Lee(1513562323) 20:33:32
baud_t除了最后的4个成员定义外,也都是最终的样子了,
John Lee(1513562323) 20:33:49
4个成员都是sfb_t<baud_t, uint32_t, 常数, 常数>的类型。
John Lee(1513562323) 20:34:36
sfb_t模板类如下:
template<typename R, typename P, uint32_t S, uint32_t W>
struct sfb_t { // Special Function Bit
enum {
LSB = S,
WIDTH = W,
MASK = (((1LL << W) - 1) << S)
};
__INLINE P read() { return P((reinterpret_cast<R*>(this)->val & MASK) >> LSB); }
__INLINE operator P() { return read(); }
__INLINE R& read(P& v)
{
v = read();
return *reinterpret_cast<R*>(this);
}
__INLINE R& write(P v, bool safe = true)
{
register R* p = reinterpret_cast<R*>(this);
register auto _v = decltype(p->val)(v);
p->changed |= MASK;
_v <<= LSB;
if ((~_v & MASK) != 0)
p->val &= ~MASK;
if (safe)
_v &= MASK;
p->val |= _v;
return *p;
}
__INLINE R& operator ()(P v, bool safe = true) { return write(v, safe); }
__INLINE R& operator =(P v) { return write(v, true); }
};
John Lee(1513562323) 20:34:49
也略去了一些不重要的部分。
John Lee(1513562323) 20:35:52
我们按前面的方法,手工处理一下sfb_t模板类:
struct sfb_t<baud_t, uint32_t, S, W> { // Special Function Bit
enum {
LSB = S,
WIDTH = W,
MASK = (((1LL << W) - 1) << S)
};
__INLINE uint32_t read() { return uint32_t((reinterpret_cast<baud_t*>(this)->val & MASK) >> LSB); }
__INLINE operator uint32_t() { return read(); }
__INLINE baud_t& read(uint32_t& v)
{
v = read();
return *reinterpret_cast<baud_t*>(this);
}
__INLINE baud_t& write(uint32_t v, bool safe = true)
{
register baud_t* p = reinterpret_cast<baud_t*>(this);
register auto _v = decltype(p->val)(v);
p->changed |= MASK;
_v <<= LSB;
if ((~_v & MASK) != 0)
p->val &= ~MASK;
if (safe)
_v &= MASK;
p->val |= _v;
return *p;
}
__INLINE baud_t& operator ()(uint32_t v, bool safe = true) { return write(v, safe); }
__INLINE baud_t& operator =(uint32_t v) { return write(v, true); }
};
John Lee(1513562323) 20:36:05
S和W都是常数。
John Lee(1513562323) 20:36:52
好了,都替换完了,我们也追踪到底了。
John Lee(1513562323) 20:38:00
在 union uart_t 和 sfb_t<baud_t, uint32_t, S, W> 类中,有一堆成员函数和数据,这些如果单独讨论,相当枯燥,也不容易理解,
惊涛骇浪(578645627) 20:38:22
复杂
51小刚(734545786) 20:38:37
John Lee(1513562323) 20:39:09
所以,我们现在原路返回,再追踪一个访问BAUD寄存器的语句的过程,看看各个类、对象是如何相互联系的,其中也会讲到那些成员函数和数据。
John Lee(1513562323) 20:41:02
语句如下:
UART0.BAUD().BRD(20).DIV_X_ONE(1);
John Lee(1513562323) 20:41:35
我做了一个big picture,显示了这个语句所牵连的所有类、对象、函数和数据。
John Lee(1513562323) 20:43:32
首先是标识符UART0,编译器根据 extern uart_t UART0 查找到了 UART0 的定义:
John Lee(1513562323) 20:44:35
各位都有图了吧?
Meng X X(357482894) 20:45:04
有了
John Lee(1513562323) 20:45:11
之后是“.”,这是一个成员访问操作符,表明“.”的左边必须是一个用户定义类型(struct, union, class)的对象(常量,变量)或引用。
游子(412240302) 20:45:11
有了
惊涛骇浪(578645627) 20:45:19
dirtwillfly(157340886) 20:45:27
有了
John Lee(1513562323) 20:45:58
根据定义:extern uart_t UART0; 编译器知道这是一个 union uart_t(用户定义)类型,
John Lee(1513562323) 20:46:37
“.”右边则必须是这个用户定义类型的成员(函数或数据),通过 union uart 的定义,编译器可知,BAUD 正是 union uart_t 的成员。
John Lee(1513562323) 20:47:35
John Lee(1513562323) 20:48:07
之后是“()”,这是一个函数调用,这表明成员 BAUD 必须是下面几种类型:
John Lee(1513562323) 20:49:01
1、成员函数,直接调用函数。
2、函数指针,通过指针间接调用函数
3、类对象,并且这个类中有重载的“函数调用操作符” -- operator ()(),这个也是调用函数(函数名是“operator ()”)
John Lee(1513562323) 20:49:56
从 uart_t::BAUD 的定义可知,它是一个 sfr_t<baud_t> 类的对象。
John Lee(1513562323) 20:50:48
John Lee(1513562323) 20:52:35
那么,sfr_t<baud_t> 类其中必有一个“()”重载操作符,我们检查一下 sfr_t<baud_t> 类就可以找到:
John Lee(1513562323) 20:54:21
我们可以看到 sfr_t<baud_t>::operator ()(),这个函数返回类型是 baud_t。
电子write_cai(772880135) 20:54:26
恩
日期:2011/12/27
John Lee(1513562323) 20:55:17
下面,我们来看看 sfr_t<baud_t>::operator ()() 函数,是如何返回了一个 baud_t 的对象,这个函数体很短:
{ return baud_t(val); }
John Lee(1513562323) 20:56:45
先岔开一下,说一点 C++ 的小知识,按 C++ 语法,baud_t(val) 表达式可能有两种语义:
John Lee(1513562323) 20:58:18
1、如果 baud_t 是用户定义类型,并且有形参类型为 val 的类型的构造函数,那么,编译器将分配空间,调用构造函数。
John Lee(1513562323) 20:59:43
否则
2、如果 val 是一个用户定义类型的对象,并且这个用户定义类型中定义了“类型转换操作符 operator::baud_t()”,
John Lee(1513562323) 21:00:47
那么 baud_t(val) 表达式将调用这个类型转换操作符,由这个操作符的函数来返回一个 baud_t 临时对象。
John Lee(1513562323) 21:01:19
如果上面两种情况都不符合,那么编译器将报错。
John Lee(1513562323) 21:02:22
我们先检查 val 的类型:
John Lee(1513562323) 21:02:38
val 是 volatile uint32_t 类型。
John Lee(1513562323) 21:05:37
再检查一下 baud_t 的构造函数定义:
好,我们找到了与 sfr_t<baud_t>::val 参数类型匹配的构造函数“形参 uint32_t volatile& r”。
John Lee(1513562323) 21:06:38
那么,baud_t(val) 表达式,编译器将分配一个 baud_t 类型的对象,
John Lee(1513562323) 21:07:50
并对这个对象调用构造函数 baud_t(uint32_t volatile& r) : ref(r), val(r), changed(0) { },实参为 sfr_t<baud_t>::val。
John Lee(1513562323) 21:09:37
编译器分配的 baud_t 类型对象有 3 个数据成员:
volatile uint32_t& ref;
uint32_t val;
uint32_t changed;
John Lee(1513562323) 21:10:47
uilliam(441346825) 21:11:12
这是什么截图软件?
batsong@21IC(6335473) 21:11:31
。。。
John Lee(1513562323) 21:12:45
这些数据成员在构造函数的初始化表达式列表中,分别被初始化了:
ref 初始化为“形参 r,实参为 sfr_t<baud_t>::val”的引用。
val 初始化为“形参 r,实参为 sfr_t<baud_t>::val”的值。
changed 初始化为0。
John Lee(1513562323) 21:13:47
休息10分钟,大家提问。
batsong@21IC(6335473) 21:15:06
还没看懂,正在消化
51小刚(734545786) 21:16:13
C++不懂啊
面朝大海(397553721) 21:16:19
惊涛骇浪(578645627) 21:16:26
俺也是
惊涛骇浪(578645627) 21:16:30
惭愧
惊涛骇浪(578645627) 21:16:39
底子不牢
batsong@21IC(6335473) 21:17:26
我倒是学过,但没用c++做过产品,不深入
chenxu_1(361017973) 21:17:48
uion也可以有构造函数?
John Lee(1513562323) 21:19:50
union类型可以有构造及析构函数,union的限制是,不允许继承和派生,并且其所有成员都不允许有构造及析构函数。
潜龙思瑞(373744463) 21:20:06
baud_t& 那个&号是什么意思
batsong@21IC(6335473) 21:20:13
引用
John Lee(1513562323) 21:20:13
引用
潜龙思瑞(373744463) 21:20:37
就是别名对吧
日期:2011/12/27
John Lee(1513562323) 21:20:47
其实是指针。
John Lee(1513562323) 21:21:09
是常量指针
John Lee(1513562323) 21:21:46
指针本身不能再改为指向另外的对象。
chenxu_1(361017973) 21:21:48
ref(r),val(r)是怎么给赋值过程
电子write_cai(772880135) 21:22:02
直接复制
John Lee(1513562323) 21:22:26
但指针指向的数据对象,可以修改。
chenxu_1(361017973) 21:22:41
那一个是引用,一个直接是变量,怎么区分的,就根据ref和val本来的类型?
John Lee(1513562323) 21:23:17
引用的定义:&
John Lee(1513562323) 21:24:05
引用类型,在定义时必须同时初始化。
John Lee(1513562323) 21:24:23
例如:
int a;
int& b = a;
John Lee(1513562323) 21:25:14
a是普通的变量,b是a的引用,在定义时就必须初始化为a.
chenxu_1(361017973) 21:25:34
哦,明白了
John Lee(1513562323) 21:25:50
对b修改时,就是对a修改。
John Lee(1513562323) 21:26:11
相当于:
int a;
int* const b = &a;
chenxu_1(361017973) 21:26:24
指向同一存储空间
John Lee(1513562323) 21:27:24
编译器内部是把引用当做指针的。
chenxu_1(361017973) 21:27:56
那这个union的物理存储是怎样的?
John Lee(1513562323) 21:30:24
只不过程序写法上,引用不需加 *,而指针需要加 *:
int &b = a; // 定义引用
b = 0; // 赋值 b, 其实是对 a。
-----------
int* const b = a; // 定义常量指针
*b = 0; // 赋值 b 指向的变量。
John Lee(1513562323) 21:31:27
可以看出,引用定义后,无法修改引用本身为另一个对象。
John Lee(1513562323) 21:31:58
因为,访问引用,实际上是访问了引用所指向的那个变量。
John Lee(1513562323) 21:33:09
而指针则不同:
b = 0; // 修改指针本身
*b = 0; // 修改指针指向的变量
John Lee(1513562323) 21:34:32
语法有修改指针本身的语法,而没有修改引用本身的方法。
John Lee(1513562323) 21:35:15
union的物理存储?
John Lee(1513562323) 21:35:54
这个跟C是一样的。
chenxu_1(361017973) 21:36:40
这个baut_t的uion太复杂了,晕了,哈哈
John Lee(1513562323) 21:39:32
好,我来提问了:
这里,struct { };是什么意思?这个 struct { };能不能省略?
John Lee(1513562323) 21:39:56
图上少了一个;
chenxu_1(361017973) 21:40:52
不能,要不然就是ref val changed就是同一存储空间了
潜龙思瑞(373744463) 21:40:59
编译器分配的 baud_t 类型对象有 3 个数据成员:
volatile uint32_t& ref;
uint32_t val;
uint32_t changed;
John Lee(1513562323) 21:41:25
OK, chenxu_1(361017973)首先答对。
日期:2011/12/27
面朝大海(397553721) 21:41:25
结构体
chenxu_1(361017973) 21:41:32
哈哈
dirtwillfly(157340886) 21:41:47
chenxu_1(361017973) 21:41:55
John Lee(1513562323) 21:42:50
好,我们继续讨论
John Lee(1513562323) 21:44:53
BAUD的实际存储空间在哪里?
John Lee(1513562323) 21:45:32
这个实际上是UART外设的BAUD寄存器。
John Lee(1513562323) 21:46:35
我们可以看到,str_t<baud_t>类型中,只有一个数据成员:
电子write_cai(772880135) 21:47:18
?
John Lee(1513562323) 21:47:45
那么,BAUD.val的存储空间也就是BAUD本身了。
可乐小子(479357804) 21:47:45
老师,我来晚了..
John Lee(1513562323) 21:49:13
先记住,str_t<typename T>::val都是实际的外设硬件寄存器。
可乐小子(479357804) 21:49:46
struct { }是定义一个结构,它的目的是定义三个成员,即老师程序中的三个变量
可乐小子(479357804) 21:50:03
老师,这是我从c++的角度解释下..
batsong@21IC(6335473) 21:50:34
可乐小子(479357804) 21:50:42
不知道对不对,c++是自学了一点点.
John Lee(1513562323) 21:52:04
在baud_t的构造函数:
__INLINE baud_t(uint32_t volatile& r) : ref(r), val(r), changed(0) { }
的初始化列表中,使用了sfr_t<baud_t>::val“实际的BAUD寄存器”来初始化了:
volatile uint32_t& ref;
uint32_t val;
John Lee(1513562323) 21:53:35
初始化后,uart_t 临时对象的ref引用,就指向了BAUD寄存器地址,val则保存了BAUD寄存器的值。
John Lee(1513562323) 21:55:46
changed表示val的状态。
初始化为0,表示val的值自初始化后,没有改变过。
John Lee(1513562323) 21:58:04
好了,uart_t临时对象初始化完成了,这个临时对象被定义的“()”操作符函数返回:
John Lee(1513562323) 22:00:01
John Lee(1513562323) 22:01:23
返回后,“UART0.BAUD()”表达式的“值”,就是这个uart_t“临时对象”。
John Lee(1513562323) 22:02:48
那么在“UART0.BAUD()”之后,我们就可以使用“.”成员访问操作符,访问 uart_t 的成员了。
John Lee(1513562323) 22:03:48
其实这个很好理解,例如:
struct foo_t { ... };
foo_t* func1();
foo_t func2();
John Lee(1513562323) 22:05:21
当我们调用了func1(),func1()返回了foo_t的指针,我们就可以紧接着使用箭头->访问foo_t的成员:
func1()-> ....
John Lee(1513562323) 22:06:34
而func2()返回了foo_t临时对象,我们就可以使用“.”访问foo_t成员:
struct foo_t { int data; };
func2().data;
John Lee(1513562323) 22:07:06
这个大家都能理解吧?
潜龙思瑞(373744463) 22:09:39
func1()和func2()都是函数???
日期:2011/12/27
batsong@21IC(6335473) 22:10:23
可以理解
John Lee(1513562323) 22:10:32
struct foo_t { int data; }; // foo_t结构
foo_t* func1(); // func1函数
foo_t func2(); // func2函数
John Lee(1513562323) 22:11:05
func1()->data;
func2().data;
batsong@21IC(6335473) 22:11:34
函数返回对象
John Lee(1513562323) 22:11:48
对!
潜龙思瑞(373744463) 22:12:23
这样用我还是第一次见到
John Lee(1513562323) 22:14:34
好,UART0.BAUD().BRD(20).DIV_X_ONE(1);这个语句,我们已经讲了前半截“UART0.BAUD()”,还剩下“.BRD(20).DIV_X_ONE(1);”
John Lee(1513562323) 22:15:36
今天就到此为止吧,大家有什么问题可以提出来,如果没有,就早点休息吧。 |