level 9
先来个简单的:
#define LIBKERN_INLINE#
include
unsigned intmin(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)来实现它,而不是用宏,更不要直接写代码。
2005年03月26日 14点03分
2
level 9
min函数如此简单,max函数呢?当然跟它一样简单,只不过return 那里要改一下顺序。大家注意到没有,这个min函数只能比较unsigned int参数。如果是long类型的呢?那就得实现一个lmin函数,如果是double类型的呢?那就得实现dmin函数。不过为什么NETBSD里没有dmin函数呢?其实,内核级的编程中,很少用到double类型。最多会用到long类型。
2005年03月26日 14点03分
4
level 9
这里C语言就没有C++方便了。C++可以实现运算符重载,或者用模板,方便地用一个函数进行不同类型数据的比较。不过它的内部,仍然是这样一种实现方法。C语言让你直接了解事情的本质。
2005年03月26日 14点03分
5
level 9
下面让我们看一个inline函数的例子,很简单的:#include
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函数。你会了吗?
2005年03月26日 14点03分
6
level 9
再来个复杂点的:#include
#include
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;}
2005年03月26日 15点03分
7
level 9
接下来讲一个简单的函数#include
#include
#undef bzero/* in case of LIBSA_USE_MEMSET */voidbzero(dstv, length)void *dstv;size_t length;{u_char *dst = dstv;while (length-- > 0) {*dst++ = 0;}}
2005年03月27日 13点03分
9
level 9
#include
#include
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;}
2005年03月27日 13点03分
10
level 9
留一个作业,有哪些函数,遵循了这样的方法?有哪些函数,没有遵循这样的方法?提示:字符串函数和文件读写函数中,这两种情况都有。大家好好找找。
2005年03月27日 13点03分
13
level 0
感谢assiss大哥,小弟我一定认真尽力去学习--------今天狂风
2005年03月28日 00点03分
14
level 0
void_remque(v)void *v;ANSI 不是规定写成void _remque(void *v)吗?
2005年03月28日 01点03分
15
level 0
fputc(),fputs()等函数就没遵循"要修改的是第一个参数,读的放在后面"
2005年03月28日 01点03分
16
level 9
很多人都对FILE函数的设计不是很满意,参数比较乱。如果历史能够重来,应该把FILE函数重新设计一遍,FILE *参数都统一放在前面。STRING函数我原来记得有一个不遵循“修改的放在前面”这样的约定的,但没找到,可能记错了?
2005年03月28日 02点03分
18
level 0
#include
#include
char source[11]="0123456789";char Result[11];int main(){ swab(source,Result,strlen(source)); printf("\nSource:%s\tResult:%s",source,Result); return 0;}----------------------顺便问一下,为什么我将char source[11]="0123456789";char Result[11];放在主函数中输出结果就会有乱码(XP sp2+Dev-C++4.9.9.2):Source:0123456789 Result:1032547698纤?鮳0123456789
2005年03月28日 02点03分
20
level 9
因为你的Result没有初始化。它的内容都是一些随机的东西。而swab只负责写10个字符,它没有顺手帮你写上一个'\0',呵呵。在swab语句后面写上一个Result[10]=0;再试试
2005年03月28日 02点03分
21
level 9
昨天没来,竟然就到第二页了。看来现在C语言吧还是比较兴旺的。可惜我的时间越来越少了。唉。今天结合一两个小函数,讲一些设计有指针的函数的注意事项1、bcmpintbcmp(b1, b2, length)const void *b1, *b2;size_t length;//这种函数的定义法是K & R法,前面提过了。比较奇怪吧。我觉得大家还是用ANSI C推荐的写法比较好。当然这种写法也不错,我有时候也快被感染了。//注意参数的类型与顺序,为什么用VOID *类型前面也讲过了。这里提一提length的类型size_t.见下面。{const char *p1 = b1, *p2 = b2;//NETBSD内核有个特点,就是无论你传过来的参数是什么样的,我在函数内部再定义一个变量来接受它。这里就用const char *类型隐式转换void *类型。//像这些并非递归调用的函数,多出来一两个局部变量对程序性能并没有多么大的损失,相反整个程序会变得更清晰,更容易理解,更容易修改。if (length == 0)return(0);//如果比较的长度是0,那就不用比较了。返回值的情况见下面分析。doif (*p1++ != *p2++)//有人问了,为什么这里不测试一下p1和p2是不是NULL呢?C语言就是C语言,它假定程序员知道一切他应该知道的事。程序员可能不知道length的长度(但至少应该知道length>=0),但一定得知道p1跟p2是不是NULL.函数不会代替程序员应有的思考。break;while (--length);return(length);//返回值的情况见下面}今天主要讲两个东西。一个是size_t类型,一个是返回值的约定。在C语言的基本数据类型里,没有size_t这个东西。那它是什么东西呢?其实在很多系统里,size_t跟int一样,都是4个字节32位的一个整形变量。为什么要定义这样的一个东西呢,看起来似乎是多余的。这是因为标准C没有规定int , long等类型具体是多少位,它们是跟机器与系统相关的。如DOS下int只有16位,而windows下却是32位,在其它的系统里,它可能是64位,或只有8位等。这一切都是可能。如果你只在一个系统里写程序,似乎不用管它的平台相关性。但C语言的一个强大的特点,就是跟UNIX/LINUX息息相关,而UNIX/LINUX现在基本上可以运行在各种平台上,一个优秀的程序员在写程序的时候,不能不考虑这个程序的可移植性。如果直接用int , long等类型,到时候修改就太多了。于是有其它的一些标准规定了一些相对稳定的类型,size_t就是其中一种。size_t类型的作用就是用来表示客观世界中一些可数的数量,因此它的值一般是>=0的。它最典型的应用,就是表示字符串的大小和文件的大小。大家可以在字符串与文件的各种函数里见到它的身影。大家在使用size_t类型的时候,无需费心,直接跟int ,long等类型一样使用。只是要注意,一般情况下,size_t类型是非负值的。跟size_t类似的,还有一个time_t类型,用来衡量时间的。大家可以在时间相关的函数中见到它的大量使用。
2005年03月29日 16点03分
23
level 9
返回值的约定:C语言标准里没有规定返回值一定是什么,这得看各人的爱好。但标准函数库有一些约定的方案。比如,返回0基本是表示顺利执行,没什么错误,或者表示比较的两个数(两种类型)相等(或相同),即最正常的情况。而返回-1或其它非零值,基本是表示有错误发生。在UNIX下要结合errno等来判断发生了什么错误,或者表示比较的两个数不等(返回正数表示第一个参数大,返回负数表示第一个参数小)。所以大家在看到下面的语句的时候,基本是应该知道是什么意思了:if((errnum=some_func(some_vari))==0){//如果正常执行这个函数,就应该到这里执行一些语句}else//发生错误或其它异常,得到这里来进行处理{//UNIX/LINUX下常需要结合errno,perror等全局变量、函数等进行判断到底发生了什么错误//或者结合上面的errnum。具体得根据不同的函数具体分析。}当然,不一定所有的函数都是这样的。比如某些字符串函数,返回NULL(通常情况下NULL与0是相等的,但不一定永远相等,所以该判断NULL的地方,不能用0代替)表示有错。又比如某些文件读写函数,返回读写的字节数(通常返回-1表示出错。因为你不可能写入或读取-1字节)。
2005年03月29日 17点03分
24
level 9
没心思再举其它例子了。也是大同小异。给狂风留个作业:找一些size_t, time_t应用的例子。找一些返回值是0表示正常,和返回0表示不正常的函数例子。
2005年03月29日 17点03分
25