3 标准函数

在C语言中,有一些函数被称为“标准函数”,这些函数对于C语言来说是非常常用的函数,大多数情况下,C语言编译器的作者或者是操作系统的作者都会提供这样的库。

其中有代表性的函数包括printf、putchar、strcmp以及malloc等。如果一个程序只调用了标准函数,那么无论在Windows中还是在Linux中都可以生成相同的应用程序(这里的相同指的是源代码可以完全通用,而并不是说完全相同的可执行文件能同时在不同的系统上运行)。

如果“纸娃娃系统”中也包含这些标准函数的话,那么上述这样的应用程序就同样可以用于“纸娃娃系统”了。听起来很不错,我们来试试看吧。

要凑齐所有的标准函数,笔者实在是吃不消,于是我们只挑其中一部分来做。如果有必要的话,剩下的可以由大家来完成。如果不清楚有哪些标准函数,可以参考一些C语言的教材,或者到“纸娃娃系统”的支持页面来提问。

■■■■■

我们先来做putchar吧。这个函数的功能是在屏幕上显示一个指定的字符,只要include了就可以使用。用api_putchar可以很容易地实现这个函数的功能。

putchar.c

  1. #include "apilib.h"
  2. int putchar(int c)
  3. {
  4. api_putchar(c);
  5. return c;
  6. }

代码很简单,用不着讲解了吧。最后的return命令指定了c,这是putchar的参考手册1上面规定的。

1 笔者参考的是这个网页:http://www.linux.or.jp/JM/html/LDP_man_pages/man3/putchar.3.html(译者注:由于时间久远,原链接已失效,各位读者请参考这里:http://www.linux.com/learn/docs/man/3838-putchar3)。

■■■■■

接下来是strcmp,不过这个已经由编译器附带了,因此不需要我们再特地编写了。

那么我们就来做exit吧。exit是用来结束应用程序的函数,只要include的就可以使用2。本来exit函数有很多功能,比如实现用atexit函数对一些函数进行注册,在程序结束时可以自动调用这些注册过的函数。要实现这些功能代码就会变得很长,我们在这里就只调用api_end,做一个简单的exit函数吧。

2 笔者参考的是这个网页:http://www.linux.or.jp/JM/html/LDP_man-pages/man3/exit.3.html(译者注:由于时间久远,原链接已失效,各位读者请参考这里:http://www.linux.com/learn/docs/man/2912-exit3)。

exit.c

  1. #include "apilib.h"
  2. void exit(int status)
  3. {
  4. api_end();
  5. }

这个也用不着讲解了吧。status参数是用来向操作系统报告程序结束状态的,由于在现在的“纸娃娃系统”中完全没有用到,因此这里就直接忽略了。

■■■■■

下面我们来做printf,这个函数连C语言的初学者都应该很熟悉了。它的功能很简单,就是将sprintf的结果输出到画面上,只要include了就可以使用了3

3 笔者参考的是这个网页:http://www.linux.or.jp/JM/html/LDP_man-pages/man3/printf.3.html(译者注:由于时间久远,原链接已失效,各位读者请参考这里:http://www.linux.com/learn/docs/man/4138-sprintf3)。

不过printf还真是一个比较难写的函数,因为它的调用方式不是固定的,例如:

  1. printf("hello, world\n");
  2. printf("a = %d (%x) ", a, a);

像上面这样,通过使用%d和%x之类的转义符,会导致参数的数量发生变化。这样的函数到底应当如何声明呢?

只要完成了函数的声明,接下来只要调用sprintf,然后再调用api_putstr0就搞定了。可问题是sprintf应该怎样调用呢?参数的数量是不固定的呀……

说那么多好像有点吓唬大家了,实际上程序并不长哦。

printf.c

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. #include "apilib.h"
  4. int printf(char *format, ...)
  5. {
  6. va_list ap;
  7. char s[1000];
  8. int i;
  9. va_start(ap, format);
  10. i = vsprintf(s, format, ap);
  11. api_putstr0(s);
  12. va_end(ap);
  13. return i;
  14. }

先来看声明部分,直接写了一个省略号“…”,看上去很奇怪吧,其实这是C语言的语法,并不是笔者的错哦(笑)。

这个“…”的部分中传递的参数,可以使用va_list来获取,只要include了就可以使用了。使用时先通过va_start进行初始化,最后再用va_end来扫尾。

同时,有一个版本的sprintf是可以接受va_list作为参数的,名字叫vsprintf,使用这个函数就可以完成处理了。vsprintf也是编译器附带的,可以直接使用。

虽然上面对“…”形式的参数做了讲解,不过这种形式并不常用,大家随便看看就可以了。

■■■■■

最后我们来做malloc和free,这两个函数只要include了就可以使用了4

4 笔者参考的是这个网页:http://www.linux.or.jp/JM/html/LDP_man-pages/man3/malloc.3.html(译者注:由于时间久远,原链接已失效,各位读者请参考这里:http://www.linux.com/learn/docs/man/2634-calloc3)。

可能大家会觉得,用api_malloc和api_free不就可以轻松实现了吗?事实上可没有那么简单。标准函数的free无需指定size,因此我们需要将malloc时指定的size找个地方存放起来。

malloc.c

  1. void *malloc(int size)
  2. {
  3. char *p = api_malloc(size + 16);
  4. if (p != 0) {
  5. *((int *) p) = size;
  6. p += 16;
  7. }
  8. return p;
  9. }

free.c

  1. void free(void *p)
  2. void free(void *p)
  3. {
  4. char *q = p;
  5. int size;
  6. if (q != 0) {
  7. q -= 16;
  8. size = *((int *) q);
  9. api_free(q, size + 16);
  10. }
  11. return;
  12. }

size的值到底应该存放在哪里呢?我们在api_malloc的时候特地多分配了16字节的空间出来,然后将size存放在那里,在free的时候则执行相反的操作。size是int型,其实只需要占用4字节的内存空间,不过内存地址为16字节的倍数时,CPU的处理速度有时候可以更快,因此在这里就用了16字节(这样从api_malloc返回的内存地址就一定是16字节的倍数)。

■■■■■

到这里,标准函数就编写完成了。由于这部分内容只是顺带提及,因此上述程序代码并未包含在本书附送的光盘中,有需要的读者请自己输入代码吧。像printf这样的函数,如果可以使用的话应该还是很方便的呢。