签到

05月06日
尚未签到

共有回帖数 0

    战神

    等级:
    先来个简单的:
    #define LIBKERN_INLINE
    #include lib/libkern/libkern.h

    unsigned int
    min(a, b)
    unsigned int a, b;
    {
    return (a  b ? a : b);
    }

    这个函数是什么作用,我想不用我多说了吧?
    这是一个非常简单的函数,简单到了极点,但它透露出来的信息不是那么简单。
    #define LIBKERN_INLINE
    这个define的作用,是告诉其它源代码,这是内核级(KERN)的一个库函数(LIB),它是INLINE的。什么叫INLINE函数?查书去。
    为什么要用INLINE函数,而不用宏呢?比如 #define min((a), (b)) ((a)(b)?(a):(b)),这样是不是更好,速度更快?
    不是的,宏定义有一个致命的缺点:编译器不帮你检查参数类型!如果你传递了一个错误类型的参数给一个宏,它还是会以为是正常的。
    而定义成函数就好多了,编译器会检查的。
    但定义成函数又有个缺点:调用函数,要压栈,保护现场,函数执行完之后又要恢复现场,出栈。花费的时间太多,对于内核级的函数来说,这是不可容忍的。
    所以定义成INLINE函数。至于是如何实现的,这比较复杂,内核有它的办法,而我们,如果想用INLINE函数,现在有了好的选择:标准C99已经定义了INLINE,具体请参考它。
    这个函数告诉我们,如果有一个很简单的功能,请用内联函数(即INLINE FUNCTION)来实现它,而不是用宏,更不要直接写代码��
    那是不是所有函数都用INLINE来实现更好?
    当然不是。内联函数的定义,是有限制的。比如有多个循环(for,while),比如递归函数,都不可以声明成内联函数。
    如果一个比较大的函数,它的执行时间已经大大超过了调用它的时间,那么定义成内联函数已经失去了它的意义,反而会让程序变大(内联函数就是在每一个调用它的地方直接展开代码,这样调用多少次,程序里就会有多少个这样的代码,体积会变大)。

    所以,内联函数,仅仅是在,一个很小功能的函数实现的时候用。它的作用,是很好地替代宏��
    min函数如此简单,max函数呢?当然跟它一样简单,只不过return 那里要改一下顺序。
    大家注意到没有,这个min函数只能比较unsigned int参数。如果是long类型的呢?
    那就得实现一个lmin函数,如果是double类型的呢?那就得实现dmin函数。
    不过为什么NETBSD里没有dmin函数呢?其实,内核级的编程中,很少用到double类型。最多会用到long类型��
    这里C语言就没有C++方便了。C++可以实现运算符重载,或者用模板,方便地用一个函数进行不同类型数据的比较。不过它的内部,仍然是这样一种实现方法。C语言让你直接了解事情的本质��
    下面让我们看一个inline函数的例子,很简单的:

    #include stdio.h

    inline void fun_a(int a)
    {
    if(a==1)printf("1n");
    else printf("not 1n");
    }

    int main()
    {
    fun_a(10000);
    }
    在mingw+gcc3.2.3下编译通过。
    这就是inline函数。你会了吗?
    再来个复杂点的:

    #include sys/types.h
    #include sys/systm.h

    struct queue {//定义一个队列结构,队列是什么?看书去!有单向的,双向的……而这个程序,定义的是双向的,有next(下一个),有prev(前一个)
    struct queue *q_next, *q_prev;
    };

    /*
    * insert an element into a queue 插队啦
    */

    void
    _insque(v1, v2)
    void *v1;
    void *v2;//为什么要用void *类型?呵呵,留个作业
    {
    struct queue *elem = v1, *head = v2;
    struct queue *next;
    //搞清楚赋值的顺序与关系。是谁插在谁的后面?
    next = head-q_next;
    elem-q_next = next;
    head-q_next = elem;
    elem-q_prev = head;
    next-q_prev = elem;
    }

    /*
    * remove an element from a queue删掉队列中的一个元素。注意,这里没有清空它占用的内存,为什么?
    */

    void
    _remque(v)
    void *v;
    {
    struct queue *elem = v;
    struct queue *next, *prev;

    next = elem-q_next;
    prev = elem-q_prev;
    next-q_prev = prev;
    prev-q_next = next;
    elem-q_prev = 0;
    }
    接下来讲一个简单的函数
    #include sys/cdefs.h
    #include lib/libkern/libkern.h

    #undef bzero /* in case of LIBSA_USE_MEMSET */

    void
    bzero(dstv, length)
    void *dstv;
    size_t length;
    {
    u_char *dst = dstv;

    while (length--  0) {
    *dst++ = 0;
    }
    }
    #include sys/cdefs.h
    #include lib/libkern/libkern.h
    bzero的作用是把一段内存里的内容全部清零。
    而memset是把一段内存中的内容全部写成某个数。
    它们的作用是如此相似(实际上bzero就是用来代替memset最常用的情形--清零)
    void *
    memset(dstv, c, length)
    void *dstv;
    int c;
    size_t length;
    {
    u_char *dst = dstv;

    while (length--  0) {
    *dst++ = c;
    }
    return dstv;
    }
    那为什么要设计bzero来代替memset的这种情形呢?
    大家注意到没有,memset的后两个参数,一个是int c,一个是size_t length.
    而在绝大多数的编译器和系统里,int和size_t是一样大小的。
    因此如果你不小心传送错了参数,编译器不会警告你!
    比如你想把int *s 的前20个字节都清零,
    你可能会memset(s, 0, 20),
    但也可能会memset(s,20, 0).如果是这样,就完全错了。可编译器无动于衷。
    而程序员一般不会记忆这种参数顺序的,特别是两种类型很像的情况下,很容易搞混。
    但要改写memset也不容易,毕竟已经用得很多了。
    于是bzero诞生了。

    这个例子告诉我们,设计函数的时候,应该多考虑一些。如果能利用编译器警告的,尽量用。

    当然有些函数你没办法设计不同类型的参数,比如swap(int a, int b),那怎么办呢?C语言设计函数有一个办法,约定俗成法��
    比如strcat这个函数,是把一个字符串连到另外一个字符串后面,
    当然你也可以这样理解,在一个字符串后面添加另外一个字符串。

    C语言怎么理解呢?它约定后一种理解方法。
    比如
    char *
    strcat(s, append)
    char *s;//第一个参数是目的字符串,即要增加(修改的)的
    const char *append;//第二个只是用来读的,因此声明成const,避免修改
    {
    char *t = s;

    for (; *t; ++t)
    ;
    while ((*t++ = *append++) != '')
    ;
    return (s);
    }
    再看strcpy

    char *
    strcpy(to, from)
    char *to;//要修改的
    const char *from;//要读的,声明成const
    {
    char *save = to;

    for (; (*to = *from) != ''; ++from, ++to);
    return(save);
    }

    再看sprintf
    sprintf(char *buf/*要修改的*/, const char *fmt/*只读的*/, ...);
    看出来了吗?都是要修改的是第一个参数,读的放在后面。
    这就是所谓的C语言约定俗成的定义函数参数的方法。这样会大大减少大家记忆参数的数量,也就大大减少了出错的可能性。
    但有一些函数因为历史原因,并没有遵循这样的方法。也有些人自己写函数,不遵循这样的方法。
    在这里,我建议大家遵循这样的方法,不仅方便别人,更重要的是,方便你自己。跟大家接轨,你才能发展得更好��

    楼主 2016-02-25 13:21 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知