C语言教程-Linux源码
先来个简单的:
#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("1\n");
else printf("not 1\n");
}
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;
}为什么要用VOID *类型为参数呢?
void *是一种很特殊的类型,特殊在它可以接受任何类型的指针,比如int *, long *,甚至其它指针,如struct _some_struct *等。
这样其它函数在调用这个函数的时候,就不用做类型转换了,可以直接把某指针作为参数传递给这个函数。
但是void *类型的参数不能直接使用,因为编译器不知道你要用的内存有多大。char *s,
*s解引用的是1个字节,int *s, *s解引用的是sizeof(int)个字节。而void *s, *s解引用的是多少字节?sizeof(void)???不知道。
所以在使用void *参数之前,必需进行类型转换。
本函数中,就是转换成了struct queue *,利用的是隐性类型转换。接下来讲一个简单的函数
#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++) != '\0')
;
return (s);
}
再看strcpy
char *
strcpy(to, from)
char *to;//要修改的
const char *from;//要读的,声明成const
{
char *save = to;
for (; (*to = *from) != '\0'; ++from, ++to);
return(save);
}
再看sprintf
sprintf(char *buf/*要修改的*/, const char *fmt/*只读的*/, ...);
看出来了吗?都是要修改的是第一个参数,读的放在后面。
这就是所谓的C语言约定俗成的定义函数参数的方法。这样会大大减少大家记忆参数的数量,也就大大减少了出错的可能性。
但有一些函数因为历史原因,并没有遵循这样的方法。也有些人自己写函数,不遵循这样的方法。
在这里,我建议大家遵循这样的方法,不仅方便别人,更重要的是,方便你自己。跟大家接轨,你才能发展得更好。留一个作业,有哪些函数,遵循了这样的方法?有哪些函数,没有遵循这样的方法?
提示:字符串函数和文件读写函数中,这两种情况都有。大家好好找找。
进入C语言程序设计视频教程专区
Word教程网 | Excel教程网 | Dreamweaver教程网 | Fireworks教程网 | PPT教程网 | FLASH教程网 | PS教程网 |
HTML教程网 | DIV CSS教程网 | FLASH AS教程网 | ACCESS教程网 | SQL SERVER教程网 | C语言教程网 | JAVASCRIPT教程网 |
ASP教程网 | ASP.NET教程网 | CorelDraw教程网 |