首页 > 代码库 > 自已动手写Redis【简单动态字符串序列一】
自已动手写Redis【简单动态字符串序列一】
第一章 简单动态字符串
2.1 引言
字符串String是程序设计中最为常见的一种数据结构,也是最为重要的一种数据结构,Hello World!这个最为精典的程序,是绝大部份人学习一种程序设计语言的入门程序,在这个最为精典的入门程序中,Hello World!就是字符串类型,字符串可以用于软件中信息的提醒、保存等,Redis中key都是String类型的,因此了解String类型对于我们了解Redis以及动手写Redis都是非常有益的。
2.2 字符串基本概念
以上是我们最为熟悉的Hello World!程序,这两段程序的输出结果都是一样的,数据结构上采用的也都是字符串,但是两者之间数据的存储区域以及数据的访问方式是完全不同的,程序2-1(a)与2-1(b)分别对应的字符串为常量字符串以及非常量字符串,那么两者之间有什么区别呢?
#include<stdio.h> int main() { char *hello = "hello world!"; printf("%s\n",hello); return 0; } (a) | #include<stdio.h> int main() { char hello[] = "hello world!"; printf("%s\n",hello); return 0; } (b) |
代码 2-1
2.2.1 常量/非常量
什么是常量字符串,由名字就可以看出,常量字符串就是字符串已经写死了,不容许外界对其进行修改,那么如何辩认字符串是不是常量字符串呢,常量字符串与非常量字符串一个重要区别就是在内存中存储的位置是不一样的,在程序中,将程序使用的空间大概划分成以下几个部份:
1、堆空间,在程序中堆空间一般由程序员进行分配与释放,其生命周期由程序设计人员进行控制。
2、栈空间,由编译器进行分配,用于存放程序中的局部变量、函数调用时的堆栈信息等,栈空间的大小受限于操作系统的软限制以及内存的硬限制。
3、静态存储区,该区域存储的是已初始化过的静态变量(包括局部静态变量以及全局静态态量)、初化过的全局变量以及常量字符串。
4、BSS内(未初始化的数据区),该区域主要用来存放全局未初始化变量,BSS区的数据在程序开始运行之前会被内核初始化为0或者空指针。
5、代码区,存放待执行的机器指令,该区域通常是只读存储区,避免由于对内存的误操作,导致代码运行的不正常。
下面通过一段简单的代码来说明C程序执行时的内存分配情况【代码2-2所示】,代码部分已经写了详细的注解:
#include<stdio.h> #include<stdlib.h> int a;//未初始化的全局变量存储在BSS区 static int t;//未初化的全局静态变量,存储在BSS区 int t = 0;//已初始化的全局变量,存储在静态存储区 static int m = 0;//已初始化的全局静态变量,存储在静态存储区 int main() { char c = ‘a‘;//函数局部变量,存储在栈中 static int m; //未初化的局部静态变量,存储在BSS区 static int q = 0;//已初始化的局部静态变量,存储在静态存储区 char *addr = (char *) malloc(sizeof(char)*2);//在堆中分配2字节 char *p = "Hello World!";//Hello World存放在静态存储区中 } |
代码 2-2
操作系统为了节省内存,将程序中的常量字符串存储在静态存储区,程序运行时可以共享这些常量字符串,无需在开设空间用来存储这些常量字符串,我们将存储在静态存储区中的字符串称为常量字符串,将存储在堆栈中的字符串称为非常量是字符串,非常量字符串也就意味着在程序运行的过程中可以动态生成字符串,也可以对已有的字符串进行修改,下面的代码2-3显示了常量/非常量字符串的一个重要区别。
#include<stdio.h> int main() { char *ptr = "Hello World!"; *(ptr+1) = ‘W‘; printf("%s",ptr); } (a) | #include<stdio.h> int main() { char ptr[] = "Hello World!"; *(ptr+1) = ‘W‘; printf("%s",ptr); } (b) |
代码 2-3
在上述代码2-3(a)中,Hello World!是一个常量字符串,存储在静态存储区中,程序编译是不会报错的,但是程序运行时会出现运行时错误,如下图2-4所示,这是因为在程序的执行过程中我们试图通过指针修改静态内存区中的常量字符串,而静态内存区中的常量字符串是只读属性,操作系统不允许外界对其进行修改,因此程序执行时会报运行时错误。
图2-4 运行时错误
与代码2-3(a)相比,代码2-3(b)并没有多大的改变,惟一的改变就是将指针ptr改成了数组,但是这种改变,对编译器却造成了不同的影响,在代码2-3(a)中,编译器发现*ptr是一个指针,会在栈中开设4字节的变量用来存储ptr这个指针的变量,同时发现ptr这个指针变量已经初始化了,指向Hello World!这个字符串,编译器会将Hello World!这个字符串作为常量字符串存储在静态存储区中,并将其地址放在ptr这个指针变量中,代码2-3(b)中,编译器会发现ptr是一个数组,这时编译器的行为就是用Hello World!这个字符串来初始化ptr数组,ptr数组的长度编译器在初始化的过程中会计算出来,即为Hello World!字符串的长度13【备注:C语言中的字符串是以\0进行结尾的,因此实际长度要加上1】。
自已动手写Redis【简单动态字符串序列一】