|
1楼 |
|
|
|
|
2楼 #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)来实现它,而不是用宏,更不要直接写代码。 |
|
|
|
|
3楼 当然不是。内联函数的定义,是有限制的。比如有多个循环(for,while),比如递归函数,都不可以声明成内联函数。 如果一个比较大的函数,它的执行时间已经大大超过了调用它的时间,那么定义成内联函数已经失去了它的意义,反而会让程序变大(内联函数就是在每一个调用它的地方直接展开代码,这样调用多少次,程序里就会有多少个这样的代码,体积会变大)。 所以,内联函数,仅仅是在,一个很小功能的函数实现的时候用。它的作用,是很好地替代宏。 |
|
|
|
|
4楼 大家注意到没有,这个min函数只能比较unsigned int参数。如果是long类型的呢? 那就得实现一个lmin函数,如果是double类型的呢?那就得实现dmin函数。 不过为什么NETBSD里没有dmin函数呢?其实,内核级的编程中,很少用到double类型。最多会用到long类型。 |
|
|
|
|
5楼 |
|
|
|
|
6楼 #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函数。你会了吗? |
|
|
|
|
7楼 #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; } |
|
|
|
|
8楼 为什么要用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 *,利用的是隐性类型转换。 |
|
|
|
|
9楼 #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; } } |
|
|
|
|
10楼 #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; } |
|
|
|
|
11楼 大家注意到没有,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语言设计函数有一个办法,约定俗成法。 |
|
|
|
|
12楼 当然你也可以这样理解,在一个字符串后面添加另外一个字符串。 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语言约定俗成的定义函数参数的方法。这样会大大减少大家记忆参数的数量,也就大大减少了出错的可能性。 但有一些函数因为历史原因,并没有遵循这样的方法。也有些人自己写函数,不遵循这样的方法。 在这里,我建议大家遵循这样的方法,不仅方便别人,更重要的是,方便你自己。跟大家接轨,你才能发展得更好。 |
|
|
|
|
13楼 提示:字符串函数和文件读写函数中,这两种情况都有。大家好好找找。 |
|
|
|
| 61.131.58.* |
14楼 -------- 今天狂风 |
|
|
| 61.131.58.* |
15楼 _remque(v) void *v; ANSI 不是规定写成void _remque(void *v)吗? |
|
|
| 61.131.58.* |
16楼 |
|
|
|
17楼 ANSI C99倒没有硬性规定一定要这样写,只不过推荐这样写,我也觉得这样写比较好 不过NETBSD那帮家伙受K & R 的影响实在太大,呵呵,他们的内核代码中,基本上都是K & R的写法。区别对待就行。 16: 文件系统的函数遵循的是另外一套约定俗成的方案, 即把FILE *的参数都放在最后, int fputc (int c, FILE *stream) int fputs (const char *s, FILE *stream) size_t fread (void *data, size_t size, size_t count, FILE *stream) size_t fwrite (const void *data, size_t size, size_t count, FILE *stream) 但是 int fprintf (FILE *stream, const char *template, ...) 显然就开始模糊了,它只能遵循前一种(因为有可变参数)。 不过 int fseek (FILE *stream, long int offset, int whence) 这样的函数就让人不知所措了。按FILE函数的约定,FILE *应该放在最后,但它没有理由地放在了前面。 每一套函数可能都有它自己的约定俗成的方案,一个良好的函数设计应该遵循统一的约定,虽然C语言没有硬性规定它。 |
|
|
|
|
18楼 如果历史能够重来,应该把FILE函数重新设计一遍,FILE *参数都统一放在前面。 STRING函数我原来记得有一个不遵循“修改的放在前面”这样的约定的,但没找到,可能记错了? |
|
|
|
| 61.131.58.* |
19楼 |
|
|
| 61.131.58.* |
20楼 #include <string.h> 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 |
|
|
|
21楼 而swab只负责写10个字符,它没有顺手帮你写上一个'\0',呵呵。 在swab语句后面写上一个Result[10]=0;再试试 |
|
|
|
| 61.131.58.* |
22楼 |
|
|
|
23楼 可惜我的时间越来越少了。唉。 今天结合一两个小函数,讲一些设计有指针的函数的注意事项 1、bcmp int bcmp(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,那就不用比较了。返回值的情况见下面分析。 do if (*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类型,用来衡量时间的。大家可以在时间相关的函数中见到它的大量使用。 |
|
|
|
|
24楼 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字节)。 |
|
|
|
|
25楼 给狂风留个作业: 找一些size_t, time_t应用的例子。 找一些返回值是0表示正常,和返回0表示不正常的函数例子。 |
|
|
|
|
26楼 例如int型的使用方法是不是: int main() { int a,b; scanf("%d,%d",&a,&b); if(!bcmp(&a,&b,sizeof(int))) printf("a==b"); else printf("a!=b"); return 0; } 而对于字符串,就是比较前length个字符是否相等? |
|
|
|
| 210.77.5.* |
27楼 |
|
|
|
28楼 我了解的就这一点点,具体如何转换就不清楚了。 int main() { time_t t; time(&t); printf("%ld\t%s",t,ctime(&t)); return 0; } |
|
|
|
|
29楼 可以把它看成是比较两个指针指向的内存段。 今天狂风>>>那个bcmp函数是不是对任何类型都能做比较呢? 要注意一点,这里比较的是值的相同,有时候和逻辑上的相同是不一样的。 有些复合数据,譬如含有指针成员的 struct,这样比较是没有意义的。 |
|
|
|