第9篇 #define的使用与代码运行检测方法 (中)

中山野鬼 发布于 2012/11/29 01:20
阅读 2K+
收藏 45
鬼话:说测试,怎么说到一个宏的设计上来了,测试方法,讲究方法,我们将代码可以看作和存储有关,和逻辑有关,和存储及逻辑均有关三种。除了最后一个有点难办,第二个,仅和逻辑有关,存在不同存储类型的代码,用上述方法设计,不是简化测试一种方式吗?
    现在回到 __VARLOGi32上。如果我们要打印两个整数型数据呢?在同一行上。一个简单的做法如下:
#define __UNION_EXP4STR(a,b,c,d) mkstr(a b c  d)    
#define __VARLOG2(exp1,exp2,_FMT,var1,var2) do {\
printf(__UNION_EXP4STR(exp1, = _FMT,exp2, = _FMT),var1,var2);printf("\n");} while (0)
#define __VARLOGi32_2(exp1,exp2) __VARLOG2(exp1,exp2,%d,__TYPE_I32(exp1),__TYPE_I32(exp2))

    现在我们还有个问题,目前测试信息打印,只能在printf的指定输出上。如果你的程序中,包括很多位置的信息输出,屏幕上放不下,比较好的方式,就是将输出,转向一个指定的文件,由此我们可以调整上述的代码,将printf,替换为fprintf。注意fprintf的参数。则有如下方式。
    define_attr.h的清单
#ifndef _DEFINE_ATTR_H_
#define _DEFINE_ATTR_H_
#include <stdio.h>
typedef int i32;
typedef long int i64;
#define __OUTPUT_FILE  stderr
#define __PRINT(...) fprintf(__OUTPUT_FILE,__VA_ARGS__)
#define __PRINT_POS() do {__PRINT("%s(%d){%s}\n",__FILE__,__LINE__,__func__);}while(0)    
#define __PRINT_FUNC()  __PRINT_POS()  //do {printf("%s func!\n",__func__);}while(0)

#define mkstr(a) # a
#define __UNION_SYMBOL(a,b) a ## b
#define __UNION_EXP2STR(c,d) mkstr(c  d)
#define __UNION_EXP4STR(a,b,c,d) mkstr(a b c  d)


#define __TYPE_V(type,var) (type)(var)
#define __TYPE_I32(var) __TYPE_V(i32,var)
#define __TYPE_S(var) __TYPE_V(char *,var)
#define __VARLOG(exp,_FMT,var) do {__PRINT(__UNION_EXP2STR(exp, =_FMT),var);printf("\n");} while (0)
#define __VARLOGi32(exp) __VARLOG(exp,%d,__TYPE_I32(exp))    
#define __VARLOGs(exp) __VARLOG(exp,%s,__TYPE_S(exp))

#define __VARLOG2(exp1,exp2,_FMT,var1,var2) do {\
__PRINT(__UNION_EXP4STR(exp1, = _FMT,exp2, = _FMT),var1,var2);printf("\n");} while (0)
#define __VARLOGi32_2(exp1,exp2) __VARLOG2(exp1,exp2,%d,__TYPE_I32(exp1),__TYPE_I32(exp2))

#endif

    注意    __PRINT_POS __VARLOG,__VARLOG2和以前的区别。由此你不得不关注
#define __PRINT(...) fprintf(__OUTPUT_FILE,__VA_ARGS__)
    ...和 __VA_ARGS__这两个内容。现在我隆重介绍,C语言里,最霸道的宏操作之一。可变参。
    ...表示其后可以跟任意个参数。而对应的__VA_ARGS__,表示实际传入的参数。
    那么 __PRINT(“1”);则 __VA_ARGS__ 即为 "1",
    对应展开为
    fprintf(stdout,"1");
    __PRINT(“%d\n",1);则 __VA_ARGS__ 即为"%d\n", 1,两个内容。
    由此我们不得不展开讨论一下,不是宏的可变参,函数可变参。也就是一个函数的后续参数可以任意。先给出一个简单的例子,打印的增强版本。
#include <stdarg.h>
#define  __debug_Msg_Size 1024
static char __pdebug_Msg[__debug_Msg_Size];
static void __debug_info(const char *prefix,const char *fmt, ...) {
    va_list params;    
    va_start(params, fmt);
    vsnprintf(__pdebug_Msg, __debug_Msg_Size, fmt, params);
    if (prefix){
        fprintf(stderr, " %s %s\n", prefix, __pdebug_Msg);
    }else{
        fprintf(stderr, " %s\n", __pdebug_Msg);
    }
    va_end(params);
}

    你可以去查找vsnprintf的参数使用和函数实现内容。这里不讨论,重点说一下 va_list ,va_start ,va_end。
    params你可以简单的看作是个指针,用于指向...所对应的第0个参数的存储位置。这样在
    vsnprintf(__pdebug_Msg, __debug_Msg_Size, fmt, params); params就可以正确的把那些无名的参数进行有效的指定。
    va_end,对上述程序可以说是多余。你可以看作将params和 ...的那些不知道数量,不知道名称的参数进行脱离。
    上述代码有什么存在价值呢?我们可以如下看
#define __debug_info_LOG(exp,PREFIX,fmt,...) do{if (exp){__PRINT_POS();__debug_info(PREFIX,fmt,__VA_ARGS__);}}while (0)

    以上表示 如果exp成立,则进行打印。而打印的内容包含但应当前位置,以及将PREFIX和后续内容通过debug_info进行打印。
    这还不够方便,如果我们继续宏。
#define __ASSERT_LOG(exp,fmt,...) __debug_info_LOG(exp,"ASSERT!",fmt,__VA_ARGS__)
#define __ERROR_LOG(exp,fmt,...) __debug_info_LOG(exp,"ERROR!",fmt,__VA_ARGS__)
    那么我们可以在代码中如下应用
void test(void){
    int i;
    for (i = 0 ;  i< 10 ; i++){
        __ASSERT_LOG((i > 5) && ( i < 7),"%s","more than 5!");
        __ERROR_LOG(i >= 7, "%s","more than 7!");
    }
}    
    对应当 5 < i < 7时,会打印出 ASSERT! more than 5! 的内容。
    当 i >= 7 时,又会打印出3条 ERROR! more than 7 ! 的内容。
    你通过输出的文件,进行字符串比较,可以筛选寻找ASSERT的情况,或ERROR的情况。
    当然,上述工作,你完全可以全部使用宏的方式来实现。宏的方式,占用代码多,调用函数的方式占用代码少。甚至你可以将
    if (exp){__PRINT_POS();等一股脑的都放到 __debug_info函数中去。例如
 
static void __debug_info(int flag ,const char *prefix,const char *fmt, ...) {
    if (!flag){
        return;
    }
    va_list params;    
    va_start(params, fmt);
    vsnprintf(__pdebug_Msg, __debug_Msg_Size, fmt, params);
    __PRINT_POS();
    if (prefix){
        fprintf(stderr, " %s %s\n", prefix, __pdebug_Msg);
    }else{
        fprintf(stderr, " %s\n", __pdebug_Msg);
    }
    va_end(params);
}

    但这样有个效率问题。每次是否要打印的判断,均需要调用函数。
    为了加深可变参的影响,我们设计一个,对任意级数指针,全维度空间申请的函数。
    我们先设计一个二级指针,对应全空间申请的函数。
void *malloc_2D(int size,int h,int w){
    char **pre,*p;
    int i;
    if ((h <= 0) || (h <= 0)) {return 0;}
    pre = (char **)malloc(size*h*w + sizeof(char *)*h);
    p = (char *)pre[h];
    for (i = 0 ; i < h ;i++){
        pre[i] = p;
        p += size * w;
    }    
    return pre;
}

    使用方法则有
    int ** ppi;
    ....
    ppi = (int **)malloc_2D(sizeof(int),10,20);
    为了防止如下出错。
    ppi = (int **)malloc_2D(sizeof(char),10,20);
    通常你可以做个宏。恩,宏是你反复要依赖的手段。
#define __MALLOC_2D(_T,d0,d1) (_T**)malloc_2D(sizeof(_T),10,20)
    则你可以
    ppi = __MALLOC_2D(int,10,20);
    我们再设计一个三级指针,对应全空间申请的函数
void *malloc_3D(int size ,unsigned int y,unsigned int h,unsigned int w ){
    void *pre,**pp,*p;
    int i;
    int num[3] ;
    int size_all;
    int size_data;
    int level = 2;
    int total_num;
    num[0] = y;
    num[1] = h;
    num[2] = w;
    size_data = size * num[0] * num[1] * num[2];
    if (!size_data) {return 0;}
    size_all = sizeof(void*) * num[0] + sizeof(void *)*num[1] * num[0]+ size_data;
    pre = (void *)malloc(size_all);
    p  = (void *)((void **)pre + num[0]);
    pp = (void **)pre;
    total_num = num[0];
    while (level > 0){
        int step = (level == 1) ? size : sizeof(void *);
        for (i = 0 ; i < total_num ; i++){
            *pp++ = p;
            p += step * num[3 - level];
        }    
        level --;
        total_num *= num[2 - level];

    }
    return pre;
}

    先写出上述的代码,是为了先解释下,整体逻辑。    size_data = size * num[0] * num[1] * num[2]; 表示实际存储数据空间的大小。
    sizeof(char*) * num[0] 表示我们未来的 ppp[x]的存储空间,有l个,也就是num[0]个。
    sizeof(char *)*num[0] * num[1] 表示我们未来的ppp[x][y]的存储空间,有 l * h个。也就是num[0] * num[1]个。
    由于指针,本身就是一个类型。char ** ,和char *,他们的差异,仅是在,当前存储空间中的值,所指向的另一个地址,我们应该如何判断其位宽类型。如果只是对本存储空间内的位宽来判断,则没有区别,因此对于 ppp[x]这个指针存储空间,和ppp[x][y]这个指针存储空间,我们都可以使用 char *来使用。其实使用void *更恰当。
    余下存在两种指针存储空间的赋值操作。如果我们是3维的空间,使用三级指针来索引,则对第0级指针,有l个存储空间,里面指向存储空间的仍然是指针位宽(对应的存储空间的名称为p[x]),step = sizeof(void *),但对于第1级存储空间,指向的是有size的byte宽度的空间,因此step = size;。同时你会发现,第0级是l个,而第一级并不是 h个,对应实际是l * h个。
    那么我们尝试设计一个任意级指针,对应全空间申请的函数,你可以很容易的根据malloc_3D来做对应替换,将num这个数组直接作为参数传入进来。并经过逻辑划减调整有
void *malloc_ND(int size ,unsigned int DIM,unsigned int dims[]){
    void *pre,**pp,*p;
    int i;
    int size_all;
    int level = DIM - 1;
    int total_num;
    size_all = size;
    for (i = 0 ; i < DIM ;i++){
        size_all *= dims[i];
    }
    if (!size_all) {return 0;}
    total_num = 1;
    for (i = 0 ; i < level  ; i++){
        total_num *= dims[i];
        size_all += sizeof(void *) * total_num;
    }
    pre = (void *)malloc(size_all);
    p  = (void *)((void **)pre + dims[0]);
    pp = (void **)pre;
    total_num  = 1;
    while (level > 0){
        total_num *= dims[DIM - 1 - level];
        int step = (level == 1) ? size : sizeof(void *);
        for (i = 0 ; i < total_num ; i++){
            *pp++ = p;
            p += step * dims[DIM - level];
        }    
        level --;
        
    }
    return pre;
}

    你可以如下调用。
    unsigned int aaa[4] = {2,3,4,5};
    pppp = (char ****)malloc_ND(sizeof(char),4,aaa);
    但如果你希望如下调用
    pppp = (char ****)malloc_ND(sizeof(char),4,2,3,4,5);
    则我们可以开始使用可变参了。对应的代码则如下,结合上面代码的对比,注意va_arg的用法。
#include <stdarg.h>
void *nmalloc_ND(int size ,unsigned int DIM,...){
    void *pre,**pp,*p;
    int i;
    int size_all;
    int level = DIM - 1;
    int total_num;
    unsigned num;
    va_list params;    
    va_start(params, DIM);    
    size_all = size;
    for (i = 0 ; i < DIM ;i++){
        size_all *= va_arg(params,unsigned int);
    }
    va_end(params);
    
    if (!size_all) {return 0;}
    total_num = 1;
    va_start(params, DIM);    
    for (i = 0 ; i < level  ; i++){
        total_num *= va_arg(params,unsigned int);
        
        size_all += sizeof(void *) * total_num;
    }
    va_end(params);
    va_start(params, DIM);    
    pre = (void *)malloc(size_all);
    num = va_arg(params,unsigned int);
    p  = (void *)((void **)pre + num);
    pp = (void **)pre;
    total_num  = 1;

    while (level > 0){
        int step = (level == 1) ? size : sizeof(void *);
        total_num *= num;
        num = va_arg(params,unsigned int);
        for (i = 0 ; i < total_num ; i++){
            *pp++ = p;
            p += step * num;
        }    
        level --;
        
    }
    va_end(params);    
    return pre;
}

    这样做还存在一定问题,什么问题? (int ****)malloc_ND(sizeof(char),4,2,3,4);
    首先有个上述同样的错误。实际要int位宽,结果写成了char,同时,4,2,3,4,明显还少一维。
    首先,我们通常的代码,申请空间,指针级数不会很多。因此可以如下设计。
#define __MALLOC_ND(_T,n,...) malloc_ND(sizeof(_T),n,__VA_ARGS__)
#define __MALLOC_2D(_T,...) (_T **)__MALLOC_ND(sizeof(_T),2,__VA_ARGS__)
#define __MALLOC_3D(_T,...) (_T ***)__MALLOC_ND(sizeof(_T),3,__VA_ARGS__)
#define __MALLOC_4D(_T,...) (_T ****)__MALLOC_ND(sizeof(_T),4,__VA_ARGS__)

    如果还怕出错。也可以如下
#define __MALLOC_2D(_T,n1,n2) (_T **)malloc_ND(sizeof(_T),2,n1,n2)
#define __MALLOC_3D(_T,n1,n2,n3) (_T ***)malloc_ND(sizeof(_T),3,n1,n2,n3)
#define __MALLOC_4D(_T,n1,n2,n3,n4) (_T ****)malloc_ND(sizeof(_T),4,n1,n2,n3,n4_)

    我相信,总有一款适合你。   

 鬼话:写一个指定的代码,不算什么本事,能把代码的逻辑精简,使得易阅读,则同时也防止了各种类型的错误出现,而能把一类代码,通过变化参数的方式统一实现,也同样降低了错误出现概率。我希望你能理解,特别是一类代码,如何从指定某个具体任务的代码逐步演进过来。如同从malloc_2D,变更到 __MALLOC_4D

加载中
0
justjavac
justjavac
终于坐沙发了。 每篇都收藏了。
0
echoyou
echoyou
前排占座
0
MrMign
MrMign
需要好好 看看了,快出书吧。。
0
zhcosin
zhcosin
不大看得懂的说
0
iuKa
iuKa
先顶再看
0
思磊
代码都挤到一起去了,看的眼睛疼,手机看悲剧啊
0
火亮
火亮
宏确实学问多啊,用好的话代码能增色不少。不过看懂宏也需要功力啊。
返回顶部
顶部