|
发表于 2012-12-14 10:57:35
|
显示全部楼层
本帖最后由 Gorgon_Meducer 于 2012-12-14 12:43 编辑
我觉得需要解释一下隐藏在
#include ptEnter
#include ptStall
这些宏下面的内容,否则即便给出ptCounter.ci的内容也不容易懂啊……
看完了,这个技术太巧妙了!学会了,哈哈,解决了一个困扰我很久的问题~这样以后我的宏系统会更飘逸的~ 我感觉自己正在踏上混乱C语言的不归路……
既然学会了,我就尝试解释一下核心的宏标号计算部分。
很多时候,我们知道宏似乎是有一定计算能力的,比如考虑下面的代码:
- //! 在配置文件的某个地方...
- #define REG_ITEM_COUNT (13u)
- ...
- //! 在代码的具体实现之前
- //! 假设每一个REG_ITEM消耗2个BIT,所以4个REG_ITEM消耗一个字节,下面计算具体消耗的字节数,不足一个字节的按照一个字节计算
- #define REQ_ITEM_COUNT_BYTE ((((REG_ITEM_COUNT) / 2) + 3) / 4)
- //! 所以在代码上我们可以这么写
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {0};
复制代码 上面的代码是没有任何问题,体现了宏自动计算的强大。但这种强大是表面上的,宏其实并没有聪明到让我们可以忘乎所以的地步,比如下
面的场合:
- #define __REQ_INIT(__COUNT, __VALUE) (__VALUE),
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {MREPEAT(REQ_ITEM_COUNT_BYTE, __REQ_INIT, 0xFF)};
复制代码 这里我解释一下,MREPEAT是一个很有用的宏,他的作用是把另外一个宏,比如这里的__REQ_INIT重复指定的次数,比如这里的
REQ_ITEM_COUNT_BYTE次,这里被重复的宏__REQ_INIT需要接受一个参数,也就是这里的0xFF。所以很容易看出,这个代码的
意图就是根据REQ_ITEM_COUNT_BYTE的数量,将所有的字节都初始化为0xFF。
问题出来了,宏MREPEAT实际上只能接受实际的数值,也就是说,这里的REQ_ITEM_COUNT_BYTE必须是一个常数,而不能是任
何表达式,这是宏MREPEAT的内容限制的,不相信,我们可以看看MREPEAT的内容片段:
- #define TPASTE2( a, b) a##b
- /*! \brief Macro repeat.
- *
- * This macro represents a horizontal repetition construct.
- *
- * \param count The number of repetitious calls to macro. Valid values range from 0 to MREPEAT_LIMIT.
- * \param macro A binary operation of the form macro(n, data). This macro is expanded by MREPEAT with
- * the current repetition number and the auxiliary data argument.
- * \param data Auxiliary data passed to macro.
- *
- * \return <tt>macro(0, data) macro(1, data) ... macro(count - 1, data)</tt>
- */
- #define MREPEAT(count, macro, data) TPASTE2(MREPEAT, count)(macro, data)
- #define MREPEAT0( macro, data)
- #define MREPEAT1( macro, data) MREPEAT0( macro, data) macro( 0, data)
- #define MREPEAT2( macro, data) MREPEAT1( macro, data) macro( 1, data)
- #define MREPEAT3( macro, data) MREPEAT2( macro, data) macro( 2, data)
- #define MREPEAT4( macro, data) MREPEAT3( macro, data) macro( 3, data)
- ...
- #define MREPEAT254(macro, data) MREPEAT253(macro, data) macro(253, data)
- #define MREPEAT255(macro, data) MREPEAT254(macro, data) macro(254, data)
- #define MREPEAT256(macro, data) MREPEAT255(macro, data) macro(255, data)
复制代码 则上面的代码实际上会被展开为:
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {
- TPASTE2(MREPEAT, REQ_ITEM_COUNT_BYTE)(__REQ_INIT, 0xFF)
- };
复制代码 前面的代码编译肯定会报告错误因为宏系统并不会将REQ_ITEM_COUNT_BYTE计算后的值作为常量替换到表达式中,相反,宏系统会保持
原汁原味的一种文字替换,因此,上面的代码实际会被进一步替换为
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {
- MREPEAT##REQ_ITEM_COUNT_BYTE(__REQ_INIT, 0xFF)
- };
复制代码 也就是
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {
- MREPEAT ((((REG_ITEM_COUNT) / 2) + 3) / 4)(__REQ_INIT, 0xFF)
- };
复制代码 这就是预编译的可能结果,于是到了编译阶段编译器就会把MREPEAT当成一个函数,首先,这个函数显然不存在,于是一个implicit funciton declaration
的warning会首先扔出来,紧接着到了Link阶段,系统发彪了,说找不到函数MREPEAT在xxxx.o……当然,这个过程是我YY的,具体编译器会有不同
的行为,但不管怎么样,宏的计算能力在我们最信任它的时候掉了链子,因为我们期望的结果是这样的:
- uint8_t s_chRequests[REQ_ITEM_COUNT_BYTE] = {
- MREPEAT4(__REQ_INIT, 0xFF)
- };
复制代码 然后这个宏会被进一步展开,最终成为4个0xFF。
=============================华丽的分割线========================================
原本到这里,故事就结束了,是的,我死心了,决定把这个作为常识了,放弃了……哎……就这样吧……
然而,今天的楼主有一次震彻了我死灰一般的大叔之心……是的,故事还可以继续!
=============================华丽的分割线========================================
有时候我们会想,要是宏的计算支持变量就好了,这样我们可以在预编译的过程中获得更大的灵活度,之前我们认为这是很难做到的,现在看来这只是一个思路
问题。假设我们通过下面的方法定义了一个宏,并且期望它能起到变量的效果:这个宏变量的名字叫做_ptCounter , 并有一个初始值0x00,当然实际上这个初始值可以是任意的单子节数值,接下来,我们要将这个_ptCounter的值拆分成高
4bit和低4bit,并独立定义两个宏分别对应高4bit和低4bit:
比如,这里通过宏_ptNew0x10来表示高4bit
- #if (((_ptCounter+1)/0x10)&0xF) == 0x0
- #undef _ptNew0x10
- #define _ptNew0x10 0
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x1
- #undef _ptNew0x10
- #define _ptNew0x10 1
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x2
- #undef _ptNew0x10
- #define _ptNew0x10 2
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x3
- #undef _ptNew0x10
- #define _ptNew0x10 3
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x4
- #undef _ptNew0x10
- #define _ptNew0x10 4
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x5
- #undef _ptNew0x10
- #define _ptNew0x10 5
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x6
- #undef _ptNew0x10
- #define _ptNew0x10 6
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x7
- #undef _ptNew0x10
- #define _ptNew0x10 7
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x8
- #undef _ptNew0x10
- #define _ptNew0x10 8
- #elif (((_ptCounter+1)/0x10)&0xF) == 0x9
- #undef _ptNew0x10
- #define _ptNew0x10 9
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xA
- #undef _ptNew0x10
- #define _ptNew0x10 A
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xB
- #undef _ptNew0x10
- #define _ptNew0x10 B
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xC
- #undef _ptNew0x10
- #define _ptNew0x10 C
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xD
- #undef _ptNew0x10
- #define _ptNew0x10 D
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xE
- #undef _ptNew0x10
- #define _ptNew0x10 E
- #elif (((_ptCounter+1)/0x10)&0xF) == 0xF
- #undef _ptNew0x10
- #define _ptNew0x10 F
- #else
- #error "ptCounter: UnExpected Error."
- #endif
复制代码 同理,这里用宏_ptNew0x01对应低4bit:
- #if ((_ptCounter+1)&0xF) == 0x0
- #undef _ptNew0x01
- #define _ptNew0x01 0
- #elif ((_ptCounter+1)&0xF) == 0x1
- #undef _ptNew0x01
- #define _ptNew0x01 1
- #elif ((_ptCounter+1)&0xF) == 0x2
- #undef _ptNew0x01
- #define _ptNew0x01 2
- #elif ((_ptCounter+1)&0xF) == 0x3
- #undef _ptNew0x01
- #define _ptNew0x01 3
- #elif ((_ptCounter+1)&0xF) == 0x4
- #undef _ptNew0x01
- #define _ptNew0x01 4
- #elif ((_ptCounter+1)&0xF) == 0x5
- #undef _ptNew0x01
- #define _ptNew0x01 5
- #elif ((_ptCounter+1)&0xF) == 0x6
- #undef _ptNew0x01
- #define _ptNew0x01 6
- #elif ((_ptCounter+1)&0xF) == 0x7
- #undef _ptNew0x01
- #define _ptNew0x01 7
- #elif ((_ptCounter+1)&0xF) == 0x8
- #undef _ptNew0x01
- #define _ptNew0x01 8
- #elif ((_ptCounter+1)&0xF) == 0x9
- #undef _ptNew0x01
- #define _ptNew0x01 9
- #elif ((_ptCounter+1)&0xF) == 0xA
- #undef _ptNew0x01
- #define _ptNew0x01 A
- #elif ((_ptCounter+1)&0xF) == 0xB
- #undef _ptNew0x01
- #define _ptNew0x01 B
- #elif ((_ptCounter+1)&0xF) == 0xC
- #undef _ptNew0x01
- #define _ptNew0x01 C
- #elif ((_ptCounter+1)&0xF) == 0xD
- #undef _ptNew0x01
- #define _ptNew0x01 D
- #elif ((_ptCounter+1)&0xF) == 0xE
- #undef _ptNew0x01
- #define _ptNew0x01 E
- #elif ((_ptCounter+1)&0xF) == 0xF
- #undef _ptNew0x01
- #define _ptNew0x01 F
- #else
- #error "ptCounter: UnExpected Error."
- #endif
复制代码 到这一步,我们就获得了两个宏_ptNew0x10和_ptNew0x01分别代表一个自己的高4bit和低4bit,接下来,就要借助另外一个宏的帮助将
这两个宏组合起来形成一个新的常数——是的,其实仍然是字面上的常数,但不管怎么样,预编译系统都是看“字面上的意思的”。
- #define _ptCTR_Cons(_X, _Y) 0x ## _X ## _Y
- #define _ptCTR_Make(_X, _Y) _ptCTR_Cons(_X, _Y)
复制代码 这个宏就是将_X和_Y代表的内容组合在一起,比如_ptCTR_Make(A,B)实际上最后组合的结果就是0xAB,有了这个宏的帮助,下面的内容
就很明了:
- #undef _ptCounter
- #define _ptCounter _ptCTR_Make(_ptNew0x10, _ptNew0x01)
复制代码 这两行代码意义非凡,尤其是第一句#undef _ptCounter,这是我们站在xx城楼上向预编器庄严宣誓“旧的_ptCounter已经被打倒了!”,
然后第二句说“新的_ptCounter成立了!”——从此_ptCounter由0x00变成了0x01。
旧的桎梏被推翻了,新的秩序正在建立中,可谓百废待兴啊……只听xx城楼下密密麻麻的原代码 哗……哗……哗……可谓举国欢庆啊!
接下来首先需要考虑两个问题,第一宏变量的命名问题,如你所见,前面的红变量叫做_ptCounter,并且下面起到核心支撑作用的条件编译也都
建立在_ptCounter这个名称的基础之上,那如果我们想换一个名字怎么办呢?立马就有人说,那还不简单,再定义一重呗,例如:
- //! 这是我们自己定义的宏变量
- #define EXAMPLE_MACRO_VAR 0x04
- //!在使用前面提到的支持系统之前先做一个替换
- #define _ptCounter EXAMPLE_MACRO_VAR
- ...
- //! 在新的_ptCounter被定义以后,我们也更新EXAMPLE_MACRO_VAR
- #undef EXAMPLE_MACRO_VAR
- #define EXAMPLE_MACRO_VAR _ptCounter
复制代码 很好,很强大……但似乎有一个问题……由于.c文件是独立编译的,如果一个.c的某一个代码块里面同时用到了两个宏变量,难么显然
最后大家都会对应到_ptCounter上,这就冲突了。看来这里存在潜在的共享问题。不过不用怕,新世界一穷二白,但不缺乏勤劳善良的程序员
于是有人提出用人海战术——即,通过古老的ctrl+C和ctrl+v法则(其实我想写大x,由于你懂的原因,最后换了一个很不爽的词,因此fuck那个
xx大x)定义一揽子宏变量及其支撑预编译系统,比如_ptCounter00,_ptCounter01...这是利国利民的大事啊,就像xx水坝一样,一次施工,
造福千年——这样我们就可以在同一个.c的同一个代码块里面使用若干个宏变量了——一般不会超过8个吧?就定义8个好了……好奢侈……
又有人提出,一个字节似乎太少了,还应该支持2个字节和4个字节的宏变量,这也是利国利民的大好事,于是大家一致决定制定如下的命名规则
MACRO_CNT_Un_xx
这里n表示位数,xx表示这是第几个变量(为了支持同一个代码块里面多个宏变量),例如
MACRO_CNT_U8_00
MACRO_CNT_U16_01
...
还有人提出,是否可以开发PC工具自动生成所需的支撑系统,放在需要的模块里面,同时还可以解除每次只增加1的限制……P民(Programmer民),
只有P民能爆发出如此的智慧……让敌人暴露在P民战争的汪洋大海中!
|
|