在编写简单的C运行库(二)中主要实现了对有关文件操作函数的实现,接下来主要实现有关字符串的函数,如itoa,strcmp,strcpy,strlen函数,这些函数并没有用到系统调用,所以也就不用向实现文件操作的函数那样使用内嵌汇编,这些函数的定义都放在string.h中。实现了字符串函数之后,就大概实现了一个小型的c运行库,虽然很简略,但对于理解c库函数运行原理、所用的关键技术有了比较深刻的认识。最后用这个小的c运行库来编译运行一个简单的测试程序,用以测试我们的库能否正常的工作。

字符串函数

  字符串函数中主要是实现itoa函数有点难度,其它的都还比较的简单,所以这里主要讲下itoa函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
char *itoa(int n, char *str, int radix)
{
char digit[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *ptr = str, *base;

if (!str || radix < 2 || radix > 36)
return str;
if (radix != 10 && n < 0)
return str;
if (!n)
{
*ptr++ = '0';
*ptr = 0;
}
if (radix == 10 && n < 0)
{
*ptr++ = '-';
n = -n;
}
base = ptr;
while (n)
{
*ptr++ = digit[n % radix];
n /= radix;
}
*ptr = 0;
for (-- ptr; base < ptr; base ++, ptr --)
{
*ptr ^= *base;
*base ^= *ptr;
*ptr ^= *base;
}
return str;
}

  itoa函数功能是把一个整数转换为字符串,我们在编写前面vfprintf函数的时候其实就已经用到过,它在c编程中也是经常用到的。从上面的代码中可以看到itoa支持2-36进制的整数转换为字符串。在这个函数中只认为十进制的数才能带有”-“号,所以在代码的第15行判断该整数是否满足是十进制的负数,如果满足在数的最前面加个”-“号,其它进制的负数默认不带”-“号。21-25行根据数的进制把数的低位到高位一个一个的分离并保存到ptr字符数组中,但是输出字符串中高位应该放在前面,所以27-32行主要是对ptr字符数组做一个倒置操作。

测试库

  接下来用一个简单的程序来测试编写的运行库,测试程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "minicrt.h"


extern char **environ;

int main ( int argc, char *argv[] )
{
int i;
FILE *fp;
char **v = malloc(argc * sizeof(char *));
for (i = 0; i < argc; i ++)
{
v[i] = malloc(strlen(argv[i]) + 1);
strcpy(v[i], argv[i]);
}

fp = fopen("text.txt", "w");
for (i = 0; i < argc; i ++)
{
int len = strlen(v[i]);
printf("%d %s\n", len, v[i]);
fwrite(&len, 1, sizeof(int), fp);
fwrite(v[i], 1, len, fp);
}
fclose(fp);

fp = fopen("text.txt", "r");
for (i = 0; i < argc; i ++)
{
int len;
char *buf;

fread(&len, 1, sizeof(int), fp);
buf = malloc(len + 1);
fread(buf, 1, len, fp);
buf[len] = 0;
printf("%d %s\n", len, buf);
free(buf);
free(v[i]);
}
free(v);
fclose(fp);

while (*environ)
printf("%s\n", *environ ++);

return 0;
}

  所有库中函数的声明、类型的声明都放在了头文件minicrt.h中,没有像标准的库那样对每类库函数的声明放在单独的头文件中,如文件操作放在stdio.h中。测试程序中基本上都用到了我们前面编写过的函数,所以对于测试我们的库是最适合不过了。

  要使用库,首先我们先要用前面编写的代码文件建立一个库,怎么建立呢?我们可以用linux下的ar命令来建立一个静态库,具体的可以见下面的命令。之所以用静态库,因为这样可以省略很多不必要的工作,我们的目的仅仅为了了解库的原理和关键技术。而动态库还有很多其它方面的知识,包括装载、运行时链接等,不过了解这些工作原理正是下面要做的工作了。

1
2
cc -c -g -fno-builtin -nostdlib -fno-stack-protector entry.c malloc.c stdio.c string.c test.c
ar -rs minicrt.a malloc.o stdio.o string.o

  “-fno-builtin”指关闭GCC内置函数功能,默认情况下GCC会把strlen、strcmp等这些常用函数展开成它内部的实现。

  “-nostdlib”不使用任何来自Glibc、GCC的库文件和启动文件,它包含了-nostartfiles这个参数。

  “-fno-stack-protector”是指关闭堆栈保护功能,最近版本的GCC会在vfprintf这样的变长参数中插入堆栈保护函数,如果不关闭,使用自己写的库时会报“__stack_chk_fail”函数未定义错误。

  其中entry.c是在编写简单的C运行库(一)中说的入口函数实现,malloc.c中是有关堆的初始化和申请释放堆的函数,stdio.c包含编写简单的C运行库(二)中有关文件操作的函数,string.c包含本文中说的字符串函数的实现,test.c中则是我们的测试代码。

  链接测试程序时不能使用c的标准库,要用自己写的minicrt.a库,具体命令为:

1
ld -static -g -e MiniCrtEntry entry.o test.o minicrt.a -o test

  “-e”参数是指定入口函数,我们使用自己实现的入口函数MiniCrtEntry。

  运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cc@localhostmimicrt]$./test
6 ./test
6 ./test
XDG_SESSION_ID=248
HOSTNAME=localhost.localdomain
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.1.161 62555 22
SSH_TTY=/dev/pts/0
USER=cc
LD_LIBRARY_PATH=/usr/local/lib
.
.
.

  正如测试程序所希望的那样,程序打印出了命令行参数的总字节数,命令行参数,环境变量。可以说这个库基本上是正确的。

总结

  编写简单的c运行库到这里基本就结束了,虽然只是实现了一个很小的库,不过麻雀虽小,五脏俱全,虽然没有真实c标准库那么的高效、完全,但至少这个库实现了c标准库的核心部分,有了这个小型库,对于扩展它的其它功能还是比较容易的。实现这个库还是比较的简单,因为有《程序员自我修养》这本书作为参考,不过这边书中所实现的linux中c++运行库的全局构造和析构机制,我在linux中按它说的实现,却发现结果和它说的不太一样,test.o中的.ctors节并没有合并到crtbegin.o和crtend.o的.ctors节之间,而是合并到crtbegin.o和crtend.o的.ctors节的下面去了,至于为什么会这样,我依然没有找到这个答案,希望有人按《程序员自我修养》实现过linux下的c++库的人帮忙解惑或者讨论下。

minicrt.h参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#ifndef MINICRT_H
#define MINICRT_H

#ifdef __cplusplus
extern "C" {
#endif

/*malloc*/
#define NULL 0
void *malloc(int size);
int brk(void *addr);
void free(void *ptr);

/*IO*/
typedef int FILE;
#define EOF -1
#define O_RDONLY 00
#define O_WRONLY 01
#define O_RDWR 02
#define O_CREAT 0100 /* not fcntl */
#define O_TRUNC 01000 /* not fcntl */
#define O_APPEND 02000
#define stdin ((FILE *)0)
#define stdout ((FILE *)1)
#define stderr ((FILE *)2)

#define va_list void*
#define va_start(ap, lastarg) \
(ap = (va_list)&lastarg + sizeof(lastarg))
#define va_arg(ap, type) \
(*(type *)((ap += sizeof(type)) - sizeof(type)))
#define va_end(ap) (ap=(va_list)0)

int fclose(FILE *fp);
int fseek(FILE *stream, long offset, int whence);
FILE *fopen(const char *path, const char *mode);
int fwrite(const void *ptr, int size, int nmemb, FILE *stream);
int fread(void *ptr, int size, int nmemb, FILE *stream);
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int vfprintf(FILE *stream, const char *format, va_list ap);
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

/*string*/
char *itoa(int n, char *str, int radix);
int strcmp(const char *s1, const char *s2);
char *strcpy(char *dest, const char *src);
int strlen(const char *s);

/*crt内部*/
void CrtFataError(char *fmt, ...);
int CrtInitHeap();
int CrtInitIo();
void do_global_ctors();
typedef void (*cxa_func_t)(void*);
typedef void (*atexit_func_t)(void);
int _cxa_atexit(cxa_func_t func, void *arg, void*);
int atexit(atexit_func_t func);
void mini_crt_all_exit_routine();

#ifdef __cplusplus
}
#endif
#endif

string.c参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
char *itoa(int n, char *str, int radix)
{
char digit[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *ptr = str, *base;

if (!str || radix < 2 || radix > 36)
return str;
if (radix != 10 && n < 0)
return str;
if (!n)
{
*ptr++ = '0';
*ptr = 0;
}
if (radix == 10 && n < 0)
{
*ptr++ = '-';
n = -n;
}
base = ptr;
while (n)
{
*ptr++ = digit[n % radix];
n /= radix;
}
*ptr = 0;
for (-- ptr; base < ptr; base ++, ptr --)
{
*ptr ^= *base;
*base ^= *ptr;
*ptr ^= *base;
}
return str;
}

int strcmp(const char *s1, const char *s2)
{
int i = 0;
unsigned char *p1 = (unsigned char *)s1;
unsigned char *p2 = (unsigned char *)s2;

while (p1[i] && p2[i] && p1[i] - p2[i] == 0)
i ++;
return (p1[i] - p2[i]);
}

char *strcpy(char *dest, const char *src)
{
char *ptr = dest;

if (!ptr)
return ptr;
while (*src)
*ptr++ = *src++;
*ptr = 0;
return dest;
}

int strlen(const char *s)
{
int len = 0;

if (!s)
return 0;
while (s[len])
len ++;
return len;
}

test.c参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "minicrt.h"


extern char **environ;

int main ( int argc, char *argv[] )
{
int i;
FILE *fp;
char **v = malloc(argc * sizeof(char *));
for (i = 0; i < argc; i ++)
{
v[i] = malloc(strlen(argv[i]) + 1);
strcpy(v[i], argv[i]);
}

fp = fopen("text.txt", "w");
for (i = 0; i < argc; i ++)
{
int len = strlen(v[i]);
printf("%d %s\n", len, v[i]);
fwrite(&len, 1, sizeof(int), fp);
fwrite(v[i], 1, len, fp);
}
fclose(fp);

fp = fopen("text.txt", "r");
for (i = 0; i < argc; i ++)
{
int len;
char *buf;

fread(&len, 1, sizeof(int), fp);
buf = malloc(len + 1);
fread(buf, 1, len, fp);
buf[len] = 0;
printf("%d %s\n", len, buf);
free(buf);
free(v[i]);
}
free(v);
fclose(fp);

while (*environ)
printf("%s\n", *environ ++);

return 0;
}

相关文章:

参考链接:https://www.cnblogs.com/chengxuyuancc/archive/2013/06/07/3123550.html