宏的使用
来源:互联网 发布:甘肃政法学院网络教学 编辑:程序博客网 时间:2024/05/20 04:08
C/C++宏的使用
基础部分
1. __VA_ARGS__:用来替换任意参数部分,相当于c语言中的va_list;
例:
#define OUT(...) printf(__VA_ARGS__)
2.宏从内向外展开(例外情况见4)
原因是,当一个红的参数也是宏的时候,它会尝试先展开它的参数;
例:
max(max(1, 3), 2) => max(3, 2) =>3
3. #和##.前者用来将一个文本转化为语言内字符串,后者用来连接两个文本
例:
#define TO_STRING(s) #s
#define CAT(a,b) a#b
TO_STRING(a) => "a"
int CAT(a,b); => int ab;
4.当一个宏对它的某个参数进行#或者##时,这个参数使用点并不被替换为展开后的文本(这里的展开意思是,将参数中的其他宏展开).这句话隐含的意思是,如果这个参数会多次使用,只要不是#或者##,都会被替换为展开后文本;
例:
#defineTEST(a) a; a +1; a##a; #a
#define MAX(a, b) ((a) > (b) ? (a) : (b))
TEST(MAX(1,1)) => ((1) >(1) ? (1) : (1)); ((1) > (1) ? (1) : (1)) + 1; MAX(1, 1)MAX(1, 1);"MAX(1, 1)"
5.对#和##做特殊处理;由于一般宏在展开时,它的参数都已经被展开,而对参数应用了#和##的宏,这里的参数中的宏却不展开,所以有必要对它们进行特殊处理,以符合一般的需要
工具:
#define TO_STRING(s) TO_STRING_D(s)
#define TO_STRING_D(s) #s
#define CONN(a,b) CONN_D(a, b)
#define CONN_D(a,b) a##b
原理:利用一般宏会展开它参数的特性, TO_STRING和CONN先将参数完全展开,然后再交给实际调用#和##的宏其中, TO_STRING非常有用,除了服务一般编程外,在使用宏的预处理期编程尤为重要,是预处理期调试的重要打印手段
6.带参数宏在无参数时被视为文本,不展开
例:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
TO_STRING(MAX(1,2)) => ((1) > (2) ? (1) : (2))
TO_STRING(MAX(MAX,MAX)) => ((MAX) > (MAX) ? (MAX) : (MAX))
7.宏不支持递归
运行时函数递归的方法是,改变参数然后进行下步调用,通过对不同参数的测试来判断是否到达终点应该停止递归.
元编程递归是通过特化、偏特化或者重载,通过令终点历程不进行进一步调用的方式,来终止递归.
表面上看,由于宏并不支持结构控制(相对于函数递归的if和元编程的If模板),所以宏不支持递归,一旦预处理器发现一个宏在展开的过程中第二次出现,则停止这个宏的展开.
关于这一点的更详细说明见后文.
预处理期编程
1.数
宏没有实质上的变量,宏的数据来源是用户编码;而各种意义的常数中,可用的只有宏和整形文字常量(用cout <<TO_STRING(...); 来打印预处理期展开情况):
例:
#defineINT_5 5
const intINT_7 = 7;
TO_STRING(INT_5) => 5
TO_STRING(6) => 6
TO_STRING(INT_7) => INT_7
可见,由于预处理期早于编译器, c++常量无效;唯一的数据源只剩下宏和硬编码整数.由于一个提供一定功能的宏只有一个文字数据源,而又没有宏变量的说法,所以,要想在宏内部实现变量的效果,就只有采用特殊手段:
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
TO_STRING(INC(INC(INC(0)))) => 3
TO_STRING(DEC(DEC(5))) => 3
这样,就实现了将文字数据源转化为范围受限整数的效果;由于受宏嵌套深度和使用范围影响,这些定义不需要太多,一般不超过256左右就合适(宏自动生成的代码,比如模板类型参数, 32个以内就够用了)
2. BOOL值
由于BOOL值是非常有用的类型,是结构控制中条件转移必须的元素,所以这里需要一种手段将上述有限范围的整数转化为0和1
#defineBOOL_0 0
#defineBOOL_1 1
#defineBOOL_2 1
#defineBOOL_3 1
#defineTO_BOOL(i) CONN(BOOL_, i)
TO_STRING(TO_BOOL(3)) => 1
TO_STRING(TO_BOOL(0)) => 0
3. if-else
利用上面的BOOL值
#define IF_THEN_ELSE(condition, then,else) CONN(CONDITION_,TO_BOOL(condition))(then, else)
#define CONDITION_1(a,b) a
#define CONDITION_0(a,b) b
4. for
由于宏不允许递归,所以试图使for循环调用自身是不可能的,这里将采用其他做法
调用FOR采用下面这种格式
FOR(数据集,条件宏,增量宏)
(1)数据集是这样的格式(data1, data2,data3, ...),含括号
(2)条件宏,接受两个参数-当前迭代轮数和数据集,当条件宏判断为假,表示不再迭代
(3)增量宏,增量宏同样接受迭代轮数和数据集,它改变数据集后作为下次迭代的数据源
最终FOR返回的是一个数据集,元素数和源数据相同,用户提取其中相应的元素
#define GC_3(a, b, c)
#define FOR1(data, toConntinue, inc)IF_THEN_ELSE(toConntinue(2, data), FOR2, data GC_3)(inc(2, data), toConntinue,inc)
#define FOR2(data, toConntinue, inc)IF_THEN_ELSE(toConntinue(3, data), FOR3, data GC_3)(inc(3, data), toConntinue,inc)
#define FOR3(data, toConntinue, inc)IF_THEN_ELSE(toConntinue(4, data), FOR4, data GC_3)(inc(4, data), toConntinue,inc)
其中传入的轮数是当前轮 + 1,原因最后阐述.这里暂时无法演示FOR的使用,看下节
5.加法
#defineDIG(n) n
#define TUPLE_SEL(sz, idx,tpl) DIG(CONN(CONN(TUPLE_SEL_,sz), CONN(_,idx)) tpl)
#define TUPLE_SEL_2_0(p0,p1) p0
TO_STRING(TUPLE_SEL(2, 0, (5,10))) => 5
TUPLE_SEL的作用是从数据集tpl中,选出第idx个元素, sz表示数据集的尺寸
下面使用FOR来实现加法
#define ADD(x,y) TUPLE_SEL(2,0, FOR1((x, y), ADD_NO_END_I,ADD_INC_I))
#define ADD_NO_END_I(i,xy) TUPLE_SEL(2, 1, xy)
#define ADD_INC_I(i,xy) ADD_INC(TUPLE_SEL(2, 0, xy), TUPLE_SEL(2,1, xy))
#define ADD_INC(x,y) (INC(x), DEC(y))
也就是说,对于数据集(x, y),每次将x加1,并将y减1,然后将结果传给下轮迭代,当y减到0时,从结果数据集(x + y, 0)中取出第一个元素就是加法的结果
6.其他运算
减法和加法类似,而乘法就是多次加法,除法是多次减法
#define SUB(x, y) TUPLE_SEL(2, 0, FOR1((x, y), SUB_NO_END_I,SUB_INC_I))
#define SUB_N(x, y,n) TUPLE_SEL(2, 0, CONN(FOR,n)((x, y),SUB_NO_END_I, SUB_INC_I))
#defineSUB_NO_END_I ADD_NO_END_I
#define SUB_INC_I(i,xy) SUB_INC(TUPLE_SEL(2, 0, xy), TUPLE_SEL(2,1, xy))
#define SUB_INC(x,y) (DEC(x), DEC(y))
#define MUL(x,y) TUPLE_SEL(3, 0, FOR1((0, x, y), MUL_NO_END_I, MUL_INC_I))
#define MUL_NO_END_I(i,sxy) TUPLE_SEL(3, 2, sxy)
#define MUL_INC_I(i,sxy) MUL_INC(i,TUPLE_SEL(3, 0, sxy), TUPLE_SEL(3, 1, sxy), TUPLE_SEL(3, 2, sxy))
#define MUL_INC(i, s, x,y) (ADD_N(s, x, i), x,DEC(y))
#define DIV(x,y) TUPLE_SEL(3, 0, FOR1((0, x, y), DIV_NO_END_I, DIV_INC_I))
#define DIV_NO_END_I(i,cxy) TUPLE_SEL(3, 1, cxy)
#define DIV_INC_I(i,cxy) DIV_INC(i, TUPLE_SEL(3, 0, cxy), TUPLE_SEL(3, 1, cxy), TUPLE_SEL(3, 2, cxy))
#define DIV_INC(i, c, x,y) (INC(c),SUB_N(x, y, i), y)
这里还有一些其他运算:
#define MOD(x,y) MOD_(SUB(MUL(y, DIV(x, y)), x), y)
#define MOD_(x,y) IF_THEN_ELSE(x, SUB(y, x), 0)
#define EQUAL(x,y) BITNOT(NOT_EQUAL(x, y))
#define NOT_EQUAL(x,y) IF_THEN_ELSE(SUB(x,y), 1, SUB(y, x))
#define LESS(x,y) SUB(y, x)
#define GREATER(x,y) SUB(x,y)
#define AND(x,y) BITAND(TO_BOOL(x), TO_BOOL(y))
#define OR(x,y) BITOR(TO_BOOL(x), TO_BOOL(y))
#defineNOT(x) BITNOT(TO_BOOL(x))
#define BITAND(x,y) CONN(CONN(BITAND_,x), CONN(_,y))
#define BITOR(x,y) CONN(CONN(BITOR_,x), CONN(_,y))
#define BITXOR(x,y) CONN(CONN(BITXOR_,x), CONN(_,y))
#defineBITNOT(x) CONN(BITNOT_,x)
7.关于宏递归和嵌套循环
前面强调过,宏不支持递归,因为预处理器一发现出现过的宏就会停止展开.那上面的乘法要怎么做?乘法是用加法来实现的,不可避免的就需要嵌套FOR;一种做法是再准备另一份FOR2_0, FOR2_1...,用于嵌套中第二曾,结果就是FOR(; ; )FOR2(; ;){}.由于完全是两组宏,自然也就避免了重复出现导致预处理器拒绝展开的问题.再讨论乘法的实现之前先看点其他的:
#define A(a) B(a)
#define B(a) A(a)C(a)
#define C(a) a
TO_STRING(A(1))的结果?是A(1)1.因为预处理器在展开B时发现A已经出现过,所以拒绝再展开
TO_STRING(CONN(0, CONN(CONN(1, 2),CONN(3, 4))))结果?结果是01234,完全展开了
这就需要追究所谓"出现过的宏",精确定义;其实上面两个例子已经在一定程度上演示了怎样的宏是出现过的宏. A(1)的例子中,发生了一次根展开,根由A变成了B: A(1) -> B(1). 假如B的展开中,又出现A,那么就会有递归的嫌疑,所以B中的A调用将不会展开.而CONN中,完全没有根展开,所以整个表达式肯定是用户编码,没有递归可能,因此预处理器进行了常规展开.所以, "出现过的宏",精确定义,不是指编码重复,而是指对于宏A,在A所在的宏调用嵌套层中,其父层次及本层次已经展开的宏中,并没有包含A.比如(为了比较和演示,并不是预处理器实际展开顺序):
#defineA(a) A1(B(C(a), D(a)))
#defineC(a) C1(a)
#defineD(a) C1(a)
#defineC1(a) C(a)D(a)
A(#) => A展开成A1(...),发生根替换, A不再可用
A1( B( C (#), D (#) )) => C展开成C1,对于本分支C1的展开, A和C不可用
A1( B( C1(#), D (#) )) => D展开成C1,对于本分支C1的展开, A和D不可用
A1( B( C1(#), C1(#) )) =>把两个C1都展开,左分支A, C, C1不可用,右分支A, D, C1不可用
A1( B( C(#)D(#), C(#)D(#) )) =>左分支,由于A和C不可用,所以只有D展开,进而A, C, C1, D都不可用
A1( B( C(#)C1(#), C(#)D(#) )) =>右分支的C展开,进而A, D, C1, C都不可用
A1( B( C(#)C1(#), C1(#)D(#) )) =>左右分支都不可再展开, A1, B又未定义,所以宏展开终点
TO_STRING(A(#)) => A1(B(C(#)C1(#), C1(#)D(#)))
讲了这么多,其实就是为了精确定义哪些宏进入了黑名单(这个黑链表应该是随着预处理器处理深度,不停调整长度的).
再来说乘法的两层迭代实现方法:
对于MUL(x, y),定义data => (s, x, y);所以:
while (y != 0)
{
s += x;
--y;
}
由于宏运算只有++和--,将上面换个写法:
for (s = 0; y != 0; --y)
{
for (int i = 0; i< x; ++i)
{
++s;
}
}
最终: data => (xy,x, 0)
前面提到的矛盾就在于,外层有个FOR,而内层也有个FOR,必须要使用某种手段,令两层FOR并不冲突.再来看FOR的迭代过程:
for1( data1, test, inc)
=> //展开for1, for1已经不可用
if (test(..., data1))
{
for2 (inc(...,data), test, inc);
}
else
{
end;
}
如果把上面的表达式看作乘法的最外层循环的话,那么内层的循环就在inc(..., data)中;这个inc(..., data),就是将一个x累加到s中,也就是一个加法,也就包含有另一个for循环.上面已经指出了,在for(n)展开的时候, for1, for2,...for(n)都不可用,那么能够由于inc(..., data)中的,只有for(n + 1),所以inc(..., data) = inc(n + 1, data),而inc的实现,就是一个利用for(n + 1)开始进行的加法;最终MUL(x, y)展开:
for1
for2 for3... for(x +1) <=其中for(x + 1)就是执行++s
for2
for3 for4... for(x +2)
for3
for4 for5... for(x +3)
...
for(y)
for(y + 1) for(y +2)... for(x + y)
这样就实现了利用一组for实现两层迭代
下面是乘法的完整代码:
#define ADD_N(x, y,n) TUPLE_SEL(2, 0, CONN(FOR,n)((x, y), ADD_NO_END_I, ADD_INC_I))
#define MUL(x, y) TUPLE_SEL(3, 0, FOR1((0, x, y), MUL_NO_END_I, MUL_INC_I))
#define MUL_NO_END_I(i,sxy) TUPLE_SEL(3, 2, sxy)
#define MUL_INC_I(i,sxy) MUL_INC(i,TUPLE_SEL(3, 0, sxy), TUPLE_SEL(3, 1, sxy), TUPLE_SEL(3, 2, sxy))
#define MUL_INC(i, s, x,y) (ADD_N(s,x, i), x, DEC(y))
8.最后
宏编程最主要的作用还是代码生成,看过上面的描述后实现一个
LOOP(n, fItem, delimit)
(n = 3, fItem(n) = typename Tn, delimit= , ) =>
typename T1, typename T2, typename T3
的功能宏,已经很容易了
恩,由于boost里面已经有preprocessor库了,它的稳定性、功能、可移植性都比自己写的要高出很多;所以,这篇宏运用的文字,主要也就是让我这样的普通c++程序员有个底,到底boost那些神秘强大的宏都在怎样做成代码的,最终使用的,还是boost.
这篇本来是作为自己宏编程笔记的,毕竟被人们冠以奇技淫巧的宏编程和元编程是为库作者准备的,我也就看看,隔天就忘了...从Kevin Lynx的代码自动生成-宏递归思想中学习宏生成代码,从boost中学习宏的图灵完备性...
下面是这篇文章提到的代码
http://download.csdn.net/source/1842009
1. 防止多重包含 2
2. 条件编译 2
3. 定义字面值常量 2
4. 定义为函数 2
5. 可变参数宏 3
6. 宏组合 3
6.1 一般用法 4
6.2 当宏参数是另一个宏的时候 4
6.2.1 非'#'和'##'的情况 4
6.2.2 当有'#'或'##'的时候 4
6.3 '#'和'##'的一些应用特例 5
6.3.1 合并匿名变量名 5
6.3.2 填充结构 5
6.3.3 记录文件名 6
6.3.4 得到一个数值类型所对应的字符串缓冲大小 6
7. 其他使用例子 6
7.1 得到指定地址上的一个字节或字 6
7.2 求最大值和最小值 6
7.3 得到一个field在结构体(struct)中的偏移量 6
7.4 得到一个结构体中field所占用的字节数 7
7.5 按照LSB格式把两个字节转化为一个Word 7
7.6 按照LSB格式把一个Word转化为两个字节 7
7.7 得到一个变量的地址(word宽度) 7
7.8 得到一个字的高位和低位字节 7
7.9 返回一个比X大的最接近的8的倍数 7
7.10 将一个字母转换为大写 7
7.11 判断字符是不是10进值的数字 7
7.12 判断字符是不是16进值的数字 8
7.13 防止溢出的一个方法 8
7.14 返回数组元素的个数 8
7.15 对于IO空间映射在存储空间的结构,输入输出处理
1. 防止多重包含
防止头文件多重包含:
如下
CODE:
#ifndef MAIN_H_
#define MAIN_H_
其它内容
#endif
作用就是阻止这个头文件被多次include。多次include就会出现重复的定义情况,所以需要在每个头文件中都使用这个定义。
2. 条件编译
#ifdef _DEBUG
printf("this debug info/n");
#endif
如果没有定义_DEBUG宏,那么上面那一行是不会编译进去。但是定义了_DEBUG后,上面那行就会编译进去。
#ifdef _M_IX86
#elif defined _M_MRX000
#endif
3. 定义字面值常量
方便修改,尽量做到修改地方少。
#define PRINT_STR "你好"
main()
{
printf(PRINT_STR);
return 0;
}
4. 定义为函数
#ifdef _DEBUG
#define A(x) a(x)
#else
#define A(x) b(x)
#endif
int a(int x)
{
return x+1;
}
int b(int x)
{
return x+100;
}
int main()
{
printf ("A(10) value is %d",A(10));
return 0;
}
其实也可以定义成#define A a
但是定义成A(x)后只有A后面带一个(x)类型的编译器才会执行替换,比较安全,可以保证只替换函数而不替换变量。
5. 可变参数宏
有些时候定义一个宏来代替某个函数,但是这个函数是可变参数的话,那就需要考虑办法了
定义方法如下
#include <iostream>
using namespace std;
#define PRINT(...) cout<<(__VA_ARGS__)
#define PRINTC(...) printf(__VA_ARGS__)
int _tmain(int argc, _TCHAR* argv[])
{
//C++6.0不可运行
PRINT("FLY编译例子");
PRINT(endl);
PRINTC("%d %s %s",1,"吃饭了吗 smile MM:)","/n");
return 0;
}
6. 宏组合
也就是##和#的用法
##是连接符号,连接两个宏
#是把名字代替成字符串
6.1 一般用法
#define s5(a) supper_##a
#include <stdio.h>
void supper_printf(const char* p )
{
printf("this is supper printf:/n%s/n",p);
}
int main()
{
s5(printf)("hello owrld");
return 0;
}
#用法如下
#include <stdio.h>
#define s(p) #p
int main()
{
printf(s(p)"/n");
return 0;
}
6.2 当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开。
6.2.1 非'#'和'##'的情况
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d/n", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2)。
6.2.2 当有'#'或'##'的时候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("int max: %s/n", STR(INT_MAX)); // INT_MAX#include<climits>
这行会被展开为:
printf("int max: %s/n", "INT_MAX");
printf("%s/n", CONS(A,A)); // compile error
这一行则是:
printf("%s/n", int(AeA));
INT_MAX和A都不会再被展开,然而解决这个问题的方法很简单。加多一层中间转换宏。
加这层宏的用意是把所有宏的参数在这层里全部展开,那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数。
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) //转换宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) //转换宏
printf("int max: %s/n",STR(INT_MAX)); //INT_MAX,int型的最大值,为一个变量#include<climits>
输出为: int max: 0x7fffffff
STR(INT_MAX)-->_STR(0x7fffffff)然后再转换成字符串;
printf("%d/n", CONS(A, A));
输出为:200
CONS(A, A)-->_CONS((2), (2))-->int((2)e(2))
6.3 '#'和'##'的一些应用特例
6.3.1 合并匿名变量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous,line)
#define ANONYMOUS(type) __ANONYMOUS0(type,__LINE__)
例:ANONYMOUS(static int); 即: static int_anonymous70; 70表示该行行号;
第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int,__LINE__);
第二层: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三层: --> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开。
6.3.2 填充结构
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG
{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"}, {CLOSE, "CLOSE"}};
6.3.3 记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
6.3.4 得到一个数值类型所对应的字符串缓冲大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
这里相当于:
char buf[11];
7. 其他使用例子
7.1 得到指定地址上的一个字节或字
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
7.2 求最大值和最小值
#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )
7.3 得到一个field在结构体(struct)中的偏移量
#define FPOS( type, field ) /
( (dword) &(( type *) 0)-> field )
7.4 得到一个结构体中field所占用的字节数
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
7.5 按照LSB格式把两个字节转化为一个Word
#define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )
7.6 按照LSB格式把一个Word转化为两个字节
#define FLOPW( ray, val ) /
(ray)[0] = ((val) / 256); /
(ray)[1] = ((val) & 0xFF)
7.7 得到一个变量的地址(word宽度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
7.8 得到一个字的高位和低位字节
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
7.9 返回一个比X大的最接近的8的倍数
#define RND8( x ) ((((x) + 7) / 8 ) *8 )
7.10 将一个字母转换为大写
#define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c)- 0x20) : (c) )
7.11 判断字符是不是10进值的数字
#define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
7.12 判断字符是不是16进值的数字
#define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||/
((c) >= ''A'' && (c) <= ''F'') ||/
((c) >= ''a'' && (c) <= ''f'') )
7.13 防止溢出的一个方法
#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))
7.14 返回数组元素的个数
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
7.15 对于IO空间映射在存储空间的结构,输入输出处理
#define inp(port) (*((volatile byte *)(port)))
#define inpw(port) (*((volatile word *) (port)))
#define inpdw(port) (*((volatile dword *)(port)))
#define outp(port, val) (*((volatile byte *) (port)) = ((byte)(val)))
#define outpw(port, val) (*((volatile word *) (port)) = ((word)(val)))
#define outpdw(port, val) (*((volatile dword *) (port)) = ((dword)(val)))
- 22、宏的使用
- ASSERT 宏的使用
- 宏工具的使用
- assert()宏的使用!
- 宏的使用
- offsetof宏的使用
- C++宏的使用
- C++宏的使用
- 宏定义的使用
- 宏定义的使用
- 宏定义的使用
- velocity 宏的使用
- TARGET_IPHONE_SIMULATOR宏的使用
- 宏的一些使用
- MTVERIFY宏的使用
- 宏的使用
- __P宏的使用
- 宏定义的使用
- 模拟、SDI、AHD、网络摄像头的视频延时测量
- Linux部署Tomcat 并访问默认项目
- Quartz Spring 报错!自动注解! Couldn't retrieve trigger: ORA-00942: 表或视图不存在
- 04-面向对象(final关键字)1 04-面向对象(final关键字)2 05-面向对象(抽象类-概述).
- Java Web 程序员如何转型大数据
- 宏的使用
- java生成验证码
- SSH框架整合 基于 XML 的配置
- Java对象的创建
- 多线程进阶ThreadLocal
- c语言运算过程中的类型自动转换原则
- BZOJ4721(NOIP2016)[蚯蚓]--队列
- [AHK]用AutoHotkey当批处理,批量修改文件名
- 记录关于JavaScript 浮点数运算的精度问题