3 标准函数
在C语言中,有一些函数被称为“标准函数”,这些函数对于C语言来说是非常常用的函数,大多数情况下,C语言编译器的作者或者是操作系统的作者都会提供这样的库。
其中有代表性的函数包括printf、putchar、strcmp以及malloc等。如果一个程序只调用了标准函数,那么无论在Windows中还是在Linux中都可以生成相同的应用程序(这里的相同指的是源代码可以完全通用,而并不是说完全相同的可执行文件能同时在不同的系统上运行)。
如果“纸娃娃系统”中也包含这些标准函数的话,那么上述这样的应用程序就同样可以用于“纸娃娃系统”了。听起来很不错,我们来试试看吧。
要凑齐所有的标准函数,笔者实在是吃不消,于是我们只挑其中一部分来做。如果有必要的话,剩下的可以由大家来完成。如果不清楚有哪些标准函数,可以参考一些C语言的教材,或者到“纸娃娃系统”的支持页面来提问。
■■■■■
我们先来做putchar吧。这个函数的功能是在屏幕上显示一个指定的字符,只要include了就可以使用。用api_putchar可以很容易地实现这个函数的功能。
putchar.c
#include "apilib.h"
int putchar(int c)
{
api_putchar(c);
return c;
}
代码很简单,用不着讲解了吧。最后的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
#include "apilib.h"
void exit(int status)
{
api_end();
}
这个也用不着讲解了吧。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还真是一个比较难写的函数,因为它的调用方式不是固定的,例如:
- printf("hello, world\n");
- printf("a = %d (%x) ", a, a);
像上面这样,通过使用%d和%x之类的转义符,会导致参数的数量发生变化。这样的函数到底应当如何声明呢?
只要完成了函数的声明,接下来只要调用sprintf,然后再调用api_putstr0就搞定了。可问题是sprintf应该怎样调用呢?参数的数量是不固定的呀……
说那么多好像有点吓唬大家了,实际上程序并不长哦。
printf.c
#include <stdio.h>
#include <stdarg.h>
#include "apilib.h"
int printf(char *format, ...)
{
va_list ap;
char s[1000];
int i;
va_start(ap, format);
i = vsprintf(s, format, ap);
api_putstr0(s);
va_end(ap);
return i;
}
先来看声明部分,直接写了一个省略号“…”,看上去很奇怪吧,其实这是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
void *malloc(int size)
{
char *p = api_malloc(size + 16);
if (p != 0) {
*((int *) p) = size;
p += 16;
}
return p;
}
free.c
void free(void *p)
void free(void *p)
{
char *q = p;
int size;
if (q != 0) {
q -= 16;
size = *((int *) q);
api_free(q, size + 16);
}
return;
}
size的值到底应该存放在哪里呢?我们在api_malloc的时候特地多分配了16字节的空间出来,然后将size存放在那里,在free的时候则执行相反的操作。size是int型,其实只需要占用4字节的内存空间,不过内存地址为16字节的倍数时,CPU的处理速度有时候可以更快,因此在这里就用了16字节(这样从api_malloc返回的内存地址就一定是16字节的倍数)。
■■■■■
到这里,标准函数就编写完成了。由于这部分内容只是顺带提及,因此上述程序代码并未包含在本书附送的光盘中,有需要的读者请自己输入代码吧。像printf这样的函数,如果可以使用的话应该还是很方便的呢。