zoukankan      html  css  js  c++  java
  • 任意存储空间结构的设计

    01#define PARAM_STR0 "height"
    02#define PARAM_STR1 "width"
    03#define PARAM_STR2 "mode"
    04enum{
    05    HEIGHT_PARAM,
    06    WIDTH_PARAM,
    07    MODE_PARAM,
    08    MAX_PARAM
    09};
    10static int s_height_param;
    11static int s_width_param;
    12static int s_mode_param;

    如果一不小心,把 HEIGHT_PARAM 对应的 PARAM_STR0 写成了
    #define PARAM_STR0 "width"
    那么在做文本内容比较时,就会出错。这里拓展说下
    #define STR "string"
    是什么。这是一个字符串的定义。如下代码
    void test(void){
        printf("%s\n",STR);
    }

    void test(void){
        printf("%s\n","string");
    }
    一样 。此时,"string",7个字符存储空间(不是6个),会作为一个常量的存储空间,存放在链接后的某个区域,而这个空间是编译过程申请的。你不必在意空间问题。其和
    const char str[] = "string"; 很像。不一致的地方是,我们对str的使用更灵活。例如
    const char str[] = "string",在A这个C文件里声明。而另一个C文件B里有如下代码
    extern const char str[]; //这个通常放在和A同名的头文件里,被B这个C文件#include进来。
    void test(void){
        printf("%s\n",str);
    }
    此时,str被看作外部一个常量的存储空间(你不能对这个空间里的内容进行修改),针对B这个C文件在编译时,并不会额外申请空间,而对于前面的例子 ,B这个文件可不管那么多,看见常量,直接分配个空间使用。
    回到前面的讨论,这里我们可以尝试使用struct这个C语言的语法声明。
    struct的用法如下
    struct STRUCT_NAME{
        int i;
        char c;
        void *p;
    } struct_space;
    这里是个例子,并不代表{ } 内一定要填写什么。同时 STRUCT_NAME表示一个存储组合的名称,而struct_space表示一个存储空间的名称。类似
    char c ; char 表示一个存储类型,而c表示该存储类型的一个申请到的空间。而上述存储组合的类型是什么? struct STRUCT_NAME;
    因此你还可以如下书写
    struct STRUCT_NAME{
    ...
    };
    struct STRUCT_NAME struct_space;
    由于这里有三个内容,存储组合的名称,存储类型,存储类型对应的空间的名称。因此存储组合的名称和存储类型对应的空间的名称没有必要不同。例如,如下的方法也是对的。不过在我的团队,谁这么写,踢谁出去。
    struct S{
        char a;
        int b;
        char c[10];
    };
    struct S S;
    那么我们如何访问到a或b呢。很简单。 如下
    S.a = 0;
    S.b = S.a + 1;
    你可以尝试 printf("S.a is %d ; S.b is %d \n",S.a,S.b); 测试一下。
    S.a 和S.b都表示什么含义呢?
    S我们知道是一个空间申请后对应的名称。由于这个空间内部的组织结构是在struct S这个类型声明里描述的。因此编译器并没有默认规则能确定。由此需要你明确提示,究竟使用这个里面的哪个具体空间。这和去拜访客户一样,某某局的某某处,甚至还有某某科室。
    那么 a ,b究竟存在这个组织空间里什么地方呢?这个和编译有关。通常,首先按顺序存放。其次,保证每个变量的存储边界至少按照寻址最小单元来存储。有些情况下, 甚至按照对硬件或操作系统有利的方式存储。例如如果32位的系统下,我们将存储内容是32位对齐的。那么假设
    S的存储空间的首地址为 0x1000,那么 a也即存放在这里,但是b 并不存放在 0x1001这里,虽然a只要一个8位空间byte。因为这样会导致数据b在传递中,要读取两个 32位的空间,并进行移位的操作,所以 b通常直接存放在0x1004里,而0x1001 到0x1003这3个存储空间,无所谓,就浪费了。由此,你设计struct,需要注意以下几个习惯,当然都是鬼话,不存在必要性 :
    1、所有存储子内容,宽度越宽的,越放上面。例如 char c[10] ;那么就放在int b和char a前面。
    2、对于任意存储子内容,坚持至少使用32位宽。例如 char c[10]就应该定义成 char c[12];
    3、除了用于存储空间收集目的,否则,struct的内容不要太多。尽可能的明确每个struct的逻辑,保证内部的子内容之间存在很强的逻辑关联。
    关于第3点,展开讨论一下。例如我们我们定义个坐标,(x,y),每个坐标上有,space_mode(空间类型),space_size(空间尺寸)等4个信息。则使用
    struct COOR{
        int x;
        int y;
    };
    struct SPACE{
        int space_mode;
        int space_size;
    };
    struct SPACE_ALL{
        struct COOR coor;
        struct SPACE space;
    };

    struct SPACE_ALL{
        int x;
        int y;
        int space_mode;
        int space_size;
    };
    要好很多。虽然存储 组织上一样。因为,x,y的逻辑关联更紧密。而x 和 space_size并没有什么对等关联。这样可以保证你对上述空间操作的代码设计,逻辑更简单,描述更清晰,代码利用率也提高。
    我上面说了一个收集,这个意思是,当我们存在很多存储空间申明时,我们希望这些存储空间能够集中存放。例如 value.c中有
    int g_status;
    char *g_pmodel ;
    char *g_pcontrol_input;
    这三个全局存储空间声明。我们可以如下操作,在value.h中增加
    struct GLOBAL{
        int status;
        char *pmodel ;
        char *pcontrol_input;
    };
    extern struct GLOBAL global;
    而对应的 extern char *g_pmodel;等等都可以删除。
    而在value.c 里可以直接写一个
    GLOBAL global;
    现有可能你还看不出好处。记得我们对每个全局存储空间的新增时都要做以下几步
    1、在value.c下,写出该空间的声明,例如 char *g_pmodel;
    2、在value.h下,写出extern char *g_pmodel;
    3、必要时,对应其他C文件,需要#include "value.h"
    而现在,你可以把1,2两项的工作,缩减为一个工作。
    在value.h的 struct GLOBAL内增加 char *pmodel;就可以了。
    别小看只是省了这一步,实际工作中,少一步就少了一个可能的出错点。此处的少,和把简单的事情搞复杂不矛盾,为啥?自己想。而需要很注意的是,这里为什么 可以少了一步?是因为,struct的收集方式,针对一类空间申请是有同样的逻辑描述,例如,需要在.h文件里extern声明一下,需要在 value.c申请一下。由此,这个收集方式,当struct里有一个存储单元被正确验证过,其他存储单元,涉及到上述步骤,也就自然可正确完整。
    鬼话:人为的书写和设计,难免有错误。一个好的设计方法,包含了降低人为失误的机理或机制。
    struct还有另外一种写法,其实你在参考文献1的 6.7.2.1中可以找到详细内容。我们给出下面一个写法
    typedef struct {
        int a;
        char b;
    }GLOBAL;
    意思是 将 struct { int a ; char b ; } 这个类型定义为 GLOBAL。此时GLOBAL直接就是一个具体的存储类型了,无非是自定义的存储结构。包含了 int a ;和char b;两个存储空间。这样的好处如下:
    struct S s1;
    GLOBAL g;
    显然下面的方式更清晰。同时,S本身并不是一个类型名。所以,你可以
    struct S S;
    而GLOBAL 是一个被编译器认可的类型名(由typedef导致)。此时
    GLOBAL GLOBAL;就是非法的了。
    鬼话:约束,有时对你是种帮助。上述struct S S;的书写方式很容易搞混代码文本的逻辑理解。
    这里联合前面说的指针讨论一下。我们需要一个存储空间,里面存在的一个值,这个值指向一个地址,对应的空间是一个自定义的存储结构。那么我们可以
    GLOBAL *pG;
     (*pG).a = 0;
     (*pG).b = 1;
    鬼话: 为什么加(),因为我记不得谁的优先级更高。*还是.。这不是丢脸的事情,甚至我可以强制保持逻辑描述的争取性。比查资料,或者背考试答卷有效的多。相信 我,学校C语言考试如果出现优先级的考题,无非是因为出题者缺乏工程开发经验导致没有更有价值的题目而凑数字的出。实际工程中,即便再有经验的工程师,对 于优先级,仍然会保持()的心态,进行确认,因为有些古怪的符合标准的书写,对于不同的编译器可能有不同的实际执行。没有必要纠结这些形而上学的东西,安 心加(),不会累死机器的。
    C语言,提供了个更好的书写方式,完成上述(*pG).a等的操作。如下:
    pG->a = 0;
    pG->b = pG->a + 1;
    这样写,表示无论是对空间的读利用,还是空间的写利用,那么这样操作都可以。实际pG->a完成了什么?我们做个测试。以下给出control.c的清单,注意新添部分
    001#include <stdio.h>
    002#include <stdlib.h>
    003#include <string.h>
    004#include "define_attr.h"
    005#include "control.h"
    006#include "value.h"
    007 
    008typedef struct{
    009    int height;
    010    int width;
    011    int mode;
    012}PARAM_S, *P_PARAM_S;
    013static PARAM_S s_param;
    014static P_PARAM_S s_pparam_test = &s_param;
    015#define GET_FILE_SIZE(size,fp) do {long int pos = ftell(fp); fseek(fp,0L,SEEK_END);size = ftell(fp);fseek(fp,pos,SEEK_SET);}while(0)
    016char filename[1024];
    017static long int buf_num = 0;
    018#define TOKEN_MAX_NUM 10
    019static char *s_ptoken_pos[TOKEN_MAX_NUM];
    020 
    021#define dNEXT_LINE 0xa
    022#define dRETURN 0xd   
    023#define dSPACE_KEY 0x20
    024#define dTAB_KEY 0x9
    025#define CHECK_ALPHA(c) (((c) != dSPACE_KEY) && ((c) != dTAB_KEY))
    026#define CHECK_LINES(p,i,lines) do {lines += (p[i] == dNEXT_LINE);}while (0)
    027 
    028#define PARAM_MAX_NUM 3
    029static int split_token(char *pline,char *ppos[]);
    030static int check_grammar(int argc,char *argv[]);
    031static int set_param(int mode,int value);
    032static int print_param(void);
    033static int split_line(char *pbuf){
    034    int i;   
    035    int lines = 0;
    036    char *pline = pbuf;
    037    __PRINT_FUNC();
    038    i = 0;
    039         
    040    while (i < buf_num){
    041 
    042        CHECK_LINES(pbuf,i,lines);
    043        if (pbuf[i] == dNEXT_LINE){
    044            int tokens;
    045            pbuf[i] = 0;
    046            printf("%s \n",pline);
    047            tokens = split_token(pline,s_ptoken_pos);
    048            printf("%d\n",tokens);
    049            //printf("check return %d\n",
    050            check_grammar(tokens,s_ptoken_pos);
    051            pline = pbuf + i+1;
    052             
    053             
    054        }else  if (pbuf[i]  == dRETURN){
    055            pbuf[i] = 0;
    056            pline = pbuf+i+1;
    057        }
    058     
    059        i++;
    060    }
    061       print_param();
    062    printf("the lines is %d\n",lines);
    063 
    064    return 0;
    065}   
    066static int split_token(char *pline,char *ppos[]){
    067    int tokens = 0;
    068 
    069    __PRINT_FUNC();
    070    while ((*pline)&&(tokens < TOKEN_MAX_NUM)){
    071        if (CHECK_ALPHA(pline[0])){
    072            ppos[tokens++] = pline;
    073                        pline++;
    074            while (CHECK_ALPHA(pline[0])){
    075                if (pline[0] == 0){
    076                    goto LABEL_return_split_token;
    077                }
    078                pline++;
    079            }           
    080        }
    081        pline[0] = 0;
    082        pline++;
    083    }
    084     
    085LABEL_return_split_token:
    086    printf("the tokens is %d !\n",tokens);   
    087    return tokens;
    088}
    089#define PARAM_STR0 "height"
    090#define PARAM_STR1 "width"
    091#define PARAM_STR2 "mode"
    092enum{
    093    HEIGHT_PARAM,
    094    WIDTH_PARAM,
    095    MODE_PARAM,
    096    MAX_PARAM
    097};
    098static int print_param(void){
    099    int a =0;
    100    printf("only test %d\n",a);
    101    printf("%s -> %d\n",PARAM_STR0,s_param.height);
    102    printf("%s -> %d\n",PARAM_STR1,s_param.width);
    103    printf("%s -> %d\n",PARAM_STR2,s_pparam_test->mode);
    104    return 0;
    105}
    106static int check_grammar(int argc,char *argv[]){
    107    int value;
    108    int mode = -1;
    109    __PRINT_FUNC();
    110    if (argc != 3){
    111        return 1;
    112    }
    113     
    114    if ((strcmp(argv[0],PARAM_STR0) == 0)){
    115        mode = 0;
    116    }else if ((strcmp(argv[0],PARAM_STR1) == 0)){
    117        mode = 1;
    118    }else if ((strcmp(argv[0], PARAM_STR2) == 0)){
    119        mode = 2;
    120    }else {
    121        return 2;
    122    }
    123    if (strcmp(argv[1],"=") != 0){
    124        return 3;
    125    }
    126    value = atoi(argv[2]);
    127    set_param(mode,value);
    128    return 0;
    129}
    130static int set_param(int mode,int value){
    131    __PRINT_FUNC();
    132    switch (mode){
    133        case HEIGHT_PARAM: s_param.height = value;
    134         //printf("%s -> %d\n",PARAM_STR0,value);
    135        break;
    136        case WIDTH_PARAM: s_param.width = value;
    137        // printf("%s -> %d\n",PARAM_STR1,value);
    138         break;
    139        case MODE_PARAM:s_param.mode = value;
    140        //printf("%s -> %d\n",PARAM_STR2,value);
    141         break;
    142        default:
    143            printf("mode is error!\n");
    144    }
    145    return 0;
    146}
    147static void read_param_default(void){
    148    __PRINT_FUNC();
    149    return;
    150}
    151static int read_param_from_file(char * filename){
    152    FILE *fp;
    153    long int file_size;
    154    long int read_size;
    155    __PRINT_FUNC();
    156    fp = fopen(filename,"rt");
    157    if (fp == 0) return 1;
    158    GET_FILE_SIZE(file_size,fp);
    159    if (file_size >= CONTROL_INPUT_SIZE){
    160        fclose(fp);
    161        return 2;
    162    }
    163 
    164    buf_num = read_size = fread(g_pcontrol_input,sizeof(char),file_size,fp);
    165    g_pcontrol_input[buf_num] = 0;
    166    printf("file size is %ld,read is %ld !\n",file_size,read_size);
    167    fclose(fp);
    168     
    169    return split_line(g_pcontrol_input);
    170}
    171 
    172void control(int flag){
    173    if (flag){
    174        read_param_from_file(filename);
    175    }else{
    176        read_param_default();
    177    }
    178    return;
    179}

        由于前面测试的正确,所以我们果断将set_param里的测试注释掉。并在split_line后,统一打印出每个参数的信息。而此时各个参数已经被 收集到s_param里。注意typedef struct { ...} PARAM_S ,*P_PARAM_S;这里实际上申明了两个类型,后一个是指针类型,其对应空间里存放的值,指向上述符合结构体逻辑关系的存储空间。
        我们反汇编看看 s_param.height 和s_param_test->mode都有什么不同。对应的链接后的反汇编,如下
    00008850 <print_param>:
        8850:    b580          push    {r7, lr}
        8852:    af00          add    r7, sp, #0
        8854:    f249 03f0     movw    r3, #37104    ; 0x90f0
        8858:    f2c0 0300     movt    r3, #0
        885c:    f241 4220     movw    r2, #5152    ; 0x1420
        8860:    f2c0 0201     movt    r2, #1
        8864:    6812          ldr    r2, [r2, #0]
        8866:    4618          mov    r0, r3
        8868:    4611          mov    r1, r2
        886a:    f7ff ee66     blx    8538 <_init+0x2c>
        886e:    f249 1300     movw    r3, #37120    ; 0x9100
        8872:    f2c0 0300     movt    r3, #0
        8876:    f241 4214     movw    r2, #5140    ; 0x1414
        887a:    f2c0 0201     movt    r2, #1
        887e:    6812          ldr    r2, [r2, #0]
        8880:    4618          mov    r0, r3
        8882:    f249 110c     movw    r1, #37132    ; 0x910c
        8886:    f2c0 0100     movt    r1, #0
        888a:    f7ff ee56     blx    8538 <_init+0x2c>
        888e:    f249 1300     movw    r3, #37120    ; 0x9100
        8892:    f2c0 0300     movt    r3, #0
        8896:    f241 4214     movw    r2, #5140    ; 0x1414
        889a:    f2c0 0201     movt    r2, #1
        889e:    6852          ldr    r2, [r2, #4]
        88a0:    4618          mov    r0, r3
        88a2:    f249 1114     movw    r1, #37140    ; 0x9114
        88a6:    f2c0 0100     movt    r1, #0
        88aa:    f7ff ee46     blx    8538 <_init+0x2c>
        88ae:    f249 1300     movw    r3, #37120    ; 0x9100
        88b2:    f2c0 0300     movt    r3, #0
        88b6:    f241 32fc     movw    r2, #5116    ; 0x13fc
        88ba:    f2c0 0201     movt    r2, #1
        88be:    6812          ldr    r2, [r2, #0]
        88c0:    6892          ldr    r2, [r2, #8]
        88c2:    4618          mov    r0, r3
        88c4:    f249 111c     movw    r1, #37148    ; 0x911c
        88c8:    f2c0 0100     movt    r1, #0
        88cc:    f7ff ee34     blx    8538 <_init+0x2c>
        88d0:    f04f 0300     mov.w    r3, #0
        88d4:    4618          mov    r0, r3
        88d6:    bd80          pop    {r7, pc}
        注意一下:
        8876:    f241 4214     movw    r2, #5140    ; 0x1414
        887a:    f2c0 0201     movt    r2, #1
        887e:    6812          ldr    r2, [r2, #0]
        ==================
        8896:    f241 4214     movw    r2, #5140    ; 0x1414
        889a:    f2c0 0201     movt    r2, #1
        889e:    6852          ldr    r2, [r2, #4]
        ==================
        88b6:    f241 32fc     movw    r2, #5116    ; 0x13fc
        88ba:    f2c0 0201     movt    r2, #1
        88be:    6812          ldr    r2, [r2, #0]
        88c0:    6892          ldr    r2, [r2, #8]
        ==================
        这三块,我猜也能猜出来,就是处理 s_param.height,s_param.width ,s_param_test->mode的工作。你问我,你怎么猜不出来,其实这没什么学问,只是个经验的问题。或许你工作个几个月,就也能猜出来了。
        (注:以上具体的地址,可能根据你的机器环境有所不同。重点的是哪些指令和寄存器如 movw r2,#xxxx, ldr r2,[r2,#0]等)
        这里不强调谁能猜出来,我们看区别。
        前面两次,对r2 设置都是相同,表示都是一个存储空间里的地址。而
        ldr r2,[r0,#XX],这个XX不同。一个0,一个是4。为什么?我们回顾下PARAM_S的定义,
        typedef struct{
            int height;
            int width;
            int mode;
        }PARAM_S;
        PARAM_S s_param;
        自然,height存放在这个空间的最起始的地方,由于是32位,所以width对应的是偏移4的位置。
        那么第三块,后面一个语句和前面和很像,而且我们知道是取s_param这个空间里偏移8的位置的,也即mode的内容。但你会发现多了一次 ldr。
        ldr r2,[r2,#0] 就是 s_pparam_test的操作,将其存储空间里的值取出来,存放到r2中。这本身就是s_param的地址。由于s_param的实际地址是链接器设 置,因此,对于s_param的操作,就不需要ldr了,直接用 movw这样指令,对r2直接设置值就可以了。
        ldr r2,[r2,#8],这和前面通过 ldr r2,[r2,#4]取出s_param里width一样,无非是取出mode。
        而我特地加上了一个测试代码,对buf_num进行打印操作。你可以注意一下
        885c:    f241 4220     movw    r2, #5152    ; 0x1420
        8860:    f2c0 0201     movt    r2, #1
        8864:    6812          ldr    r2, [r2, #0]
            这表示什么含义呢?一个存储空间的访问,和一个自定义的存储结构体空间里的单元访问并没有什么指令操作上的区别。如
                8864:    6812          ldr    r2, [r2, #0] //buf_num
               887e:    6812          ldr    r2, [r2, #0]//s_param.height
                  887e:    6812          ldr    r2, [r2, #4]//s_param.widht
                  所以千万别人为s_param.height多了个.操作,就会导致计算逻辑变复杂了。这是因为编译器可以实现算出结构体内各个单元在这个整体存储空间内的值。
                  那么,我们自己怎么显示的获取这些值呢?以下,给出一个C的#define ,
    1#define __BIAS_STRUCT(type,member) (unsigned long)&(((type *)0)->member)

        那么我们将print_param修改如下
    1#define __BIAS_STRUCT(type,member) (unsigned long)&(((type *)0)->member)
    2static int print_param(void){
    3 
    4    printf("%s(%ld) -> %d\n",PARAM_STR0,__BIAS_STRUCT(PARAM_S,height),s_param.height);
    5    printf("%s(%ld)-> %d\n",PARAM_STR1,__BIAS_STRUCT(PARAM_S,width),s_param.width);
    6    printf("%s(%ld) -> %d\n",PARAM_STR2,__BIAS_STRUCT(PARAM_S,mode),s_pparam_test->mode);
    7    return 0;
    8}

        你可以编译链接,运行看一下,是否还有如下内容:
    height(0) -> 3
    width(4)-> 5
    mode(8) -> 1
        我们分析一下上面的一定。如果给入PARAM_S ,和height这两个内容,则    
        __BIAS_STRUCT(PARAM_S,height) 被替换为
        (unsigned long)&(((PARAM_S *)0)->height)
        从()的最里面看。 (PARAM_S *) 0,这是将0强制转换为(PARAM_S*),((PARAM_S *)0)->height 是取一个PARAM_S的类型结构的存储空间里height的单元。&(((PARAM_S *)0)->height) 是将这个单元的对应地址取出来。(unsigned long)是将这个地址强制转换为正整数。由于整个存储空间,是以0地址为起始,所以&(((PARAM_S *)0)->height)取出来的数减去0,自然是这个height在整个结构体内的偏移位置。我们可以反汇编看下,机器都怎么操作的。
        你可以注意到有以下代码,你忽略你的机器上的绝对地址。
        8864:    6814          ldr    r4, [r2, #0]
        8866:    4618          mov    r0, r3
        8868:    f249 01f8     movw    r1, #37112    ; 0x90f8
        886c:    f2c0 0100     movt    r1, #0
        8870:    f04f 0200     mov.w    r2, #0
        8874:    4623          mov    r3, r4
        8876:    f7ff ee60     blx    8538 <_init+0x2c>    
        由于我们printf("%s(%ld) -> %d\n",PARAM_STR0,__BIAS_STRUCT(PARAM_S,height),s_param.height);的参数改动。所以
        __BIAS_STRUCT(PARAM_S,height)最终的值存放在r2,原先r2中存放的s_param.height被挪到r3中。
        此时,    8870:    f04f 0200     mov.w    r2, #0 ,表示编译器对上述复杂的定义     (unsigned long)&(((PARAM_S *)0)->height)
        直接计算出了结果。
    鬼话:这样做有啥好处?你去看看linux内核中涉及list的相关源代码,就知道了。别怀疑,那些世界顶级的C工程师,写出的代码,实际你现在也能写 出。无非你的经验不足,尚不能灵活应用。这里送很多新手一句张狂的话,“当你抬头景仰所谓高手,牛人时,你已经输,再牛也是人,没必要怀疑他们使用了某种 你没有权利使用的高端设计方法”
        这里需要再重复的说明一下。
    #define __BIAS_STRUCT(type,member) (unsigned long)&(((type )0).member)
        是不对的。我们从另一个角度来解释。 (type)a表示,a是个存储空间。我们对a里面的值,或某个具体的值,逻辑上(必要时,机器指令有新增动作保证逻辑正确)进行转换。例如你可以看作有符号,或无符号。这对后期计算的逻辑有作用。例如
        signed char sc;
        unsigned char uc;
        sc = (signed char)127;
        uc = (unsigned char)sc;
        sc++;
        uc++;
        此时,sc里等于0,而uc里等于128.不信你打印试试。
        uc = (unsigned char)sc;表示取出sc里的值,强制转换为无符号char类型,存储到uc里。
        sc= (signed char)127;表示,对127这个值,强制转换为有符合char类型,存储到sc里。
        而(type ) 0,表示什么意思?将0,按照type的类型来理解。如果type是 PARAM_S,如下
        s = (PARAM_S)0;充其量是对s进行全部设置0的操作。此时就是有作用,也只是保存到一个PARAM_S的存储空间里。而我们要的是门牌号码,不是家 里的东西。因此 0应该我们设想的是对某个PARAM_S的存储空间的地址的描述。因此自然是(PARAM_S *)0,即,将0作为一个地址,这个地址,指向一个PARAM_S类型的存储空间。那么
        &((PARAM_S *)0->height)自然是对存储在0这个地址,我们看做PARAM_S类型的存储空间里,height这个单元的取地址操作。
        这有还有什么用?我们回顾一下开头说的易出错的问题。我们如何保证"height"和 s_param.height 对应呢?以下给出一个打击学院派,坚决不用switch的设计方法。好处后面提。先把control.c的完整代码列出,注意新增部分和注释掉的部分。
    001#include <stdio.h>
    002#include <stdlib.h>
    003#include <string.h>
    004#include "define_attr.h"
    005#include "control.h"
    006#include "value.h"
    007 
    008typedef struct{
    009    int height;
    010    int width;
    011    int mode;
    012    float testf;
    013}PARAM_S, *P_PARAM_S;
    014static PARAM_S s_param;
    015static P_PARAM_S s_pparam_test = &s_param;
    016#define PARAM_MAX_NUM 4
    017#define __BIAS_STRUCT(type,member) (unsigned long)&(((type *)0)->member)
    018#define __BIAS_PARAM(member) __BIAS_STRUCT(PARAM_S,member)
    019#define __SET_BIAS(type,ps,bias) *(type *)((void *)(ps) + bias)
    020#define PARAM_STR_MAX_SIZE 20
    021 
    022enum{
    023    _INT_MODE,
    024    _FLOAT_MODE,
    025    _MAX_MODE
    026};
    027typedef struct{
    028    char *str;
    029    unsigned long bias;
    030    char* default_value;
    031    int type;
    032}PARAM_TAB_S;
    033typedef PARAM_TAB_S * P_PARAM_TAB_S;
    034 
    035 
    036const static PARAM_TAB_S c_param_tab[PARAM_MAX_NUM] = {
    037{"width",__BIAS_PARAM(width),"0",_INT_MODE},
    038{"height",__BIAS_PARAM(height),"0",_INT_MODE},
    039{"mode",__BIAS_PARAM(mode),"0",_INT_MODE},
    040{"ftest",__BIAS_PARAM(testf),"1.0f",_FLOAT_MODE}
    041};
    042typedef  void (*_SET_FUNC)(unsigned long ,char *) ;
    043static void set_int_param(unsigned long bias,char *s){
    044    __SET_BIAS(int,s_pparam_test,bias) = atoi(s);
    045}
    046static void set_float_param(unsigned long bias,char *s){
    047    __SET_BIAS(float,s_pparam_test,bias) = (float)atof(s);
    048}
    049_SET_FUNC set_param_func[_MAX_MODE] = {set_int_param,set_float_param};
    050 
    051 
    052#define GET_FILE_SIZE(size,fp) do {long int pos = ftell(fp); fseek(fp,0L,SEEK_END);size = ftell(fp);fseek(fp,pos,SEEK_SET);}while(0)
    053char filename[1024];
    054static long int buf_num = 0;
    055#define TOKEN_MAX_NUM 10
    056static char *s_ptoken_pos[TOKEN_MAX_NUM];
    057 
    058#define dNEXT_LINE 0xa
    059#define dRETURN 0xd   
    060#define dSPACE_KEY 0x20
    061#define dTAB_KEY 0x9
    062#define CHECK_ALPHA(c) (((c) != dSPACE_KEY) && ((c) != dTAB_KEY))
    063#define CHECK_LINES(p,i,lines) do {lines += (p[i] == dNEXT_LINE);}while (0)
    064 
    065 
    066static int split_token(char *pline,char *ppos[]);
    067static int check_grammar(int argc,char *argv[]);
    068//static int set_param(int mode,int value);
    069static int print_param(void);
    070static int split_line(char *pbuf){
    071    int i;   
    072    int lines = 0;
    073    char *pline = pbuf;
    074    __PRINT_FUNC();
    075    i = 0;
    076         
    077    while (i < buf_num){
    078 
    079        CHECK_LINES(pbuf,i,lines);
    080        if (pbuf[i] == dNEXT_LINE){
    081            int tokens;
    082            pbuf[i] = 0;
    083            printf("%s \n",pline);
    084            tokens = split_token(pline,s_ptoken_pos);
    085            printf("%d\n",tokens);
    086            //printf("check return %d\n",
    087            check_grammar(tokens,s_ptoken_pos);
    088            pline = pbuf + i+1;
    089             
    090             
    091        }else  if (pbuf[i]  == dRETURN){
    092            pbuf[i] = 0;
    093            pline = pbuf+i+1;
    094        }
    095     
    096        i++;
    097    }
    098       print_param();
    099    printf("the lines is %d\n",lines);
    100 
    101    return 0;
    102}   
    103static int split_token(char *pline,char *ppos[]){
    104    int tokens = 0;
    105 
    106    __PRINT_FUNC();
    107    while ((*pline)&&(tokens < TOKEN_MAX_NUM)){
    108        if (CHECK_ALPHA(pline[0])){
    109            ppos[tokens++] = pline;
    110                        pline++;
    111            while (CHECK_ALPHA(pline[0])){
    112                if (pline[0] == 0){
    113                    goto LABEL_return_split_token;
    114                }
    115                pline++;
    116            }           
    117        }
    118        pline[0] = 0;
    119        pline++;
    120    }
    121     
    122LABEL_return_split_token:
    123    printf("the tokens is %d !\n",tokens);   
    124    return tokens;
    125}
    126#if 0
    127#define PARAM_STR0 "height"
    128#define PARAM_STR1 "width"
    129#define PARAM_STR2 "mode"
    130enum{
    131    HEIGHT_PARAM,
    132    WIDTH_PARAM,
    133    MODE_PARAM,
    134    MAX_PARAM
    135};
    136//#define __BIAS_STRUCT(type,member) (unsigned long)&(((type *)0)->member)
    137#endif
    138static int print_param(void){
    139 
    140    printf("s_param.height -> %d\n",s_param.height);
    141    printf("s_param.width -> %d\n",s_param.width);
    142    printf("s_param.mode -> %d\n",s_param.mode);
    143    printf("s_param.testf -> %f\n",s_param.testf);   
    144    return 0;
    145}
    146static int check_grammar(int argc,char *argv[]){
    147 
    148    int mode = -1;
    149    __PRINT_FUNC();
    150    if (argc != 3){
    151        return 1;
    152    }
    153    for (mode =0; mode < PARAM_MAX_NUM ; mode++){
    154        if (strcmp(argv[0],c_param_tab[mode].str) ==0) break;
    155    }
    156    if (mode >= PARAM_MAX_NUM){
    157        return 2;
    158    }
    159 
    160    if (strcmp(argv[1],"=") != 0){
    161        return 3;
    162    }
    163     
    164    set_param_func[c_param_tab[mode].type](c_param_tab[mode].bias,argv[2]);
    165 
    166    return 0;
    167}
    168#if 0
    169static int set_param(int mode,int value){
    170    __PRINT_FUNC();
    171    switch (mode){
    172        case HEIGHT_PARAM: s_param.height = value;
    173         //printf("%s -> %d\n",PARAM_STR0,value);
    174        break;
    175        case WIDTH_PARAM: s_param.width = value;
    176        // printf("%s -> %d\n",PARAM_STR1,value);
    177         break;
    178        case MODE_PARAM:s_param.mode = value;
    179        //printf("%s -> %d\n",PARAM_STR2,value);
    180         break;
    181        default:
    182            printf("mode is error!\n");
    183    }
    184    return 0;
    185}
    186#endif
    187static void read_param_default(void){
    188    int mode;
    189    __PRINT_FUNC();
    190    for (mode =0;mode < PARAM_MAX_NUM ; mode++){
    191        set_param_func[c_param_tab[mode].type](c_param_tab[mode].bias,c_param_tab[mode].default_value);
    192    }
    193    return;
    194}
    195static int read_param_from_file(char * filename){
    196    FILE *fp;
    197    long int file_size;
    198    long int read_size;
    199    __PRINT_FUNC();
    200    read_param_default();
    201    fp = fopen(filename,"rt");
    202    if (fp == 0) return 1;
    203    GET_FILE_SIZE(file_size,fp);
    204    if (file_size >= CONTROL_INPUT_SIZE){
    205        fclose(fp);
    206        return 2;
    207    }
    208 
    209    buf_num = read_size = fread(g_pcontrol_input,sizeof(char),file_size,fp);
    210    g_pcontrol_input[buf_num] = 0;
    211    printf("file size is %ld,read is %ld !\n",file_size,read_size);
    212    fclose(fp);
    213     
    214    return split_line(g_pcontrol_input);
    215}
    216 
    217void control(int flag){
    218    if (flag){
    219        read_param_from_file(filename);
    220    }else{
    221        read_param_default();
    222    }
    223    return;
    224}

    以上我分段进行解释。
    1typedef struct{
    2    int height;
    3    int width;
    4    int mode;
    5    float testf;
    6}PARAM_S, *P_PARAM_S;
     
       这里我们增加了一个float 类型的存储单元。无非是想证明这种方法对float的类型的参数也可以正确识别。
       #define __SET_BIAS(type,ps,bias) *(type *)((void *)(ps) + bias) 是用于对一个结构体存储空间里的某个单元进行访问,而通过该单元在该整体的结构体内的偏移量位置决定。例如 width在PARAM_S的偏移量是4则
       __SET_BIAS(int,s_pparam_test,width) 为
       *(int*)((void*)s_pparam_test + 4)
     
    1enum{
    2    _INT_MODE,
    3    _FLOAT_MODE,
    4    _MAX_MODE
    5};

    这里枚举了两种类型,_MAX_MODE写出来的好处在于
    _SET_FUNC set_param_func[_MAX_MODE] ;
    对应的typedef  void (*_SET_FUNC)(unsigned long ,char *) ;就不说了。前面讲过函数指针类型的定义。
    这个函数指针数组的大小可以非常简单对应,且不会书写出错。
    1typedef struct{
    2    char *str;
    3    unsigned long bias;
    4    char *default_value;
    5    int type;
    6}PARAM_TAB_S;

    这是个针对参数分析的结构体。包含了,参数在文本文件中的名词,当前参数在PARAM_S的结构体内的偏移量,default_value你可以从 read_param_default函数被调用的位置发现用途。为什么我们的config_attr 没有testf,s_param.testf还被设置了1。
    注意
    1for (mode =0; mode < PARAM_MAX_NUM ; mode++){
    2        if (strcmp(argv[0],c_param_tab[mode].str) ==0) break;
    3    }
    4     
    5    for (mode =0;mode < PARAM_MAX_NUM ; mode++){
    6        set_param_func[c_param_tab[mode].type](c_param_tab[mode].bias,c_param_tab[mode].default_value);
    7    }

        这些代码的设计。
    鬼话:以上代码,算我自己的发明创造。我不介意,学院派又开始说,for循环里调用函数指针是多么的没效率。我指向说,在代码设计中,不出错,逻辑清晰, 新增逻辑对应的新增代码量少,是开发效率。至于运行效率,那是整个设计逻辑正确,模块逻辑稳定后,优化的事情,优化和效率是两会事。如果诸位迷信学院派的 说法,不放我们比试一下,下面的新增需求。
    假设我们PARAM_S,因为设计的新增,需要增加一个参数 vec(速度) ,类型是double。那么我需要改动的地方,有几个
        首先,参数的增加,修改
    1#define PARAM_MAX_NUM 5

        和
    1typedef struct{
    2    double vec;
    3    int height;
    4    int width;
    5    int mode;
    6    float testf;
    7    }PARAM_S, *P_PARAM_S;

        如果你跟随学院派,不知到把vec任意放某个位置,是否会导致额外的已有正确代码挪动。
        其次,我们需要增加参数表的内容
    1const static PARAM_TAB_S c_param_tab[PARAM_MAX_NUM] = {
    2 
    3{"width",__BIAS_PARAM(width),"0",_INT_MODE},
    4{"height",__BIAS_PARAM(height),"0",_INT_MODE},
    5{"mode",__BIAS_PARAM(mode),"0",_INT_MODE},
    6{"ftest",__BIAS_PARAM(testf),"1.0f",_FLOAT_MODE},
    7{"vec",__BIAS_PARAM(vec),"5.02",_DOUBLE_MODE}
    8};

        注意,这里并需要 将 vec的位置和PARAM_S里申明位置相同,此处放在了最后。
        由于是double的处理。因此多了
    1enum{
    2    _INT_MODE,
    3    _FLOAT_MODE,
    4    _DOUBLE_MODE,
    5    _MAX_MODE
    6};

        并新增了函数
    1static void set_double_param(unsigned long bias ,char *s){
    2    __SET_BIAS(double,s_pparam_test,bias) = atof(s);
    3}

        同时扩建了函数指针的内容。
    1_SET_FUNC set_param_func[_MAX_MODE] = {set_int_param,set_float_param,set_double_param};

        对应,很傻乎乎的学了学院派的一个书写方法,在print_param函数中增加了内容。
    1static int print_param(void){
    2     
    3    printf("s_param.height -> %d\n",s_param.height);
    4    printf("s_param.width -> %d\n",s_param.width);
    5    printf("s_param.mode -> %d\n",s_param.mode);
    6    printf("s_param.testf -> %f\n",s_param.testf);
    7    printf("s_param.vec -> %f\n",s_param.vec);       
    8    return 0;
    9}

    其实正确的方式应当如下:
    1static int print_param(void){
    2    int mode;
    3    for (mode =0; mode < PARAM_MAX_NUM; i++){
    4        print_param_type[c_param_tab[mode].type](mode);
    5    }
    6}

    使用函数指针进行调用打印。由于是打印,我就不搞那么复杂了。但是后果就是你没新增一个参数就得多写一次printf的内容。
    鬼话:为什么新手写代码,一定逻辑复杂,或内容较多,就总是错来错去。是因为一开始,真把简单的事情搞简单了。当我们把简单的事情搞复杂,如 原先 直接 if 一个个用常量的比较方法,并在 set_param 中用switch一个个比较设置,这个看似简单,却不能让已有代码尽可能的被后续的新增逻辑复用。相信我,努力的琢磨如何让具备类似逻辑描述的代码,真正 的实现同类描述,例如
    set_param_func[c_param_tab[mode].type](c_param_tab[mode].bias,c_param_tab[mode].default_value);
    或 print_param_type[c_param_tab[mode].type](mode);(此对应代码未写出)
    将有助于你提升代码设计的效率。
    以下进一步,给出更极端的例子,不过属于少儿不宜,请勿模仿。
    我们看一下
    1static void set_double_param(unsigned long bias ,char *s){
    2    __SET_BIAS(double,s_pparam_test,bias) = atof(s);
    3}
    4static void set_int_param(unsigned long bias,char *s){
    5    __SET_BIAS(int,s_pparam_test,bias) = atoi(s);
    6}
    7static void set_float_param(unsigned long bias,char *s){
    8    __SET_BIAS(float,s_pparam_test,bias) = (float)atof(s);
    9}

    他们有很多相似处。你将不相似的地方提出来如下
    1static void set_X_param(unsigned long bias,char *s){
    2    __SET_BIAS(X,s_pparam_test,bias) = XX(s);
    3}

    那么我们可以如下定义
    1#define __FUNC_SET_PARAM(X,XX,XXX) static void set_##X##_param(unsigned long bias ,char *s){\
    2__SET_BIAS(X,s_pparam_test,bias) = XX(s);\
    3}

    则上述的代码可以用以下的替换
    01#if 1
    02#define __FUNC_SET_PARAM(X,XX) static void set_##X##_param(unsigned long bias ,char *s){\
    03__SET_BIAS(X,s_pparam_test,bias) = XX(s);\
    04}
    05__FUNC_SET_PARAM(double,atof)
    06__FUNC_SET_PARAM(int,atoi)
    07__FUNC_SET_PARAM(float,(float)atof)
    08#else
    09static void set_double_param(unsigned long bias ,char *s){
    10    __SET_BIAS(double,s_pparam_test,bias) = atof(s);
    11}
    12static void set_int_param(unsigned long bias,char *s){
    13    __SET_BIAS(int,s_pparam_test,bias) = atoi(s);
    14}
    15static void set_float_param(unsigned long bias,char *s){
    16    __SET_BIAS(float,s_pparam_test,bias) = (float)atof(s);
    17}
    18#endif

    set_##X##_param,如果XX为 int ,则会被替换为,set_int_param
    为什么我说这是个少儿不宜的事情,其实即便你已经成年,也不该如此书写代码。道理很简单。如果是
    set_string_param的函数,内部不能
    __SET_BIAS(X,s_pparam_test,bias) = XX(s);简单替换怎么办?
    鬼话:别嫉妒那些更高级的语言,有什么重载,多态的名词,其实无非如上述一样的设计。任何语言,如果在一个函数里,存在一条语句,可以针对不同类型进行类 似的操作,如同 A = B+C,而不在乎A,B,C的类型。他实际编译后,和你上述写的各种类型的函数实现,并使用对应函数指针进行链接没有区别。无非,C语言,需要比较土的写 出来。
    我们需要实现上述设计,脱离不了struct这个万能的打包器。为什么?我们看下如下定义
    01typedef struct{
    02    int zero : 1;
    03    unsigned int overflow : 1;
    04    unsigned int data : 5;
    05    unsigned int extend : 1;
    06} FLAG_S;
    07 FLAG_S ff;
    08 ff.zero = 3;
    09 ff.overflow = 1;
    10 ff.data = 27;
    11 ff.extend = 3;

     你尝试将这些值打印出来看看。
    1printf("%x : %d : %d : %d : %d\n",*(char*)&ff,ff.zero,ff.overflow,ff.data,ff.extend);

     这里表示 ,ff所对应的存储空间,第0为,给zero使用,所以即便你赋值为3,仍然设置个1,由于是int型,带符号,所以输出为-1,而
     ff.extend虽然也只有一位,也是只能设置1,虽然赋值为3,但打印确实1
     16进制打印出的 ffffffef,实际真正有效的ef对应二进制为
      1 1 1 0 1 1 1 1
      实际与上述ff内的单元对应如下
      1 | 1 1 0 1 1| 1 | 1
      extend | data | overflow | zero
    同时你应该好好读读,参考文献1 的6.7.8.6的例子的解释。其实参考文献 1中 设计struct的内容,都应该好好读读。

         如果你仍然坚持使用书本的C语言设计方法,不妨我们继续增加逻辑。对各种参数我们增加范围限制,如果当超出范围,自动将参数设置成默认值。看看那种方法能更快,更有效,更保证正确性的设计出来。

    鬼话:模块化编程在局部细节上的一个思想就是尽可能的抽象出雷同逻辑。使用数组,结构体,特别是函数指针,宏定义,等方式来实现。抽象抽象再抽象,即是学 好数学的学问,也是编好程序的学问。努力的把直白的“简单”问题,搞抽象的”复杂“起来,将非常有助于在复杂的直白问题时,”简单“的用抽象的方法实现。

        
  • 相关阅读:
    Photoshop 基础三 制作简单按钮
    Photoshop 基础二 快捷键
    Photoshop 基础一 安装
    Winform 基础二 最小化 最大化 关闭 点击任务栏隐藏显示 点击鼠标左键移动窗体
    Winform 基础一 panel
    ASP 基础三 SQL指令
    回文&升降 数位dp
    双向链表()
    Two strings HDU
    Just do it HDU
  • 原文地址:https://www.cnblogs.com/shihao/p/2766847.html
Copyright © 2011-2022 走看看