我们大家都知道,如果我们声明了一个变量或者对象,内存里面首先会有一个指针,指向具体的内容。比如说,我声明了一个字符串“hello”,那就有一个字符串指针指向这个字符串。在32位系统下,这个指针就是32位的,占了我4个字节的空间。字符是16位的,hello一共5个字母,又占了我10个字节。那是不是说,这个字符串就占了我14个字节呢?
我不知道。
动手找答案吧!
下面的讨论都是基于windows+.net环境,仅仅是因为我比较熟悉而已。Linux+c的话,肯定会有些不一样,但是道理应该是不变的。
就用大家最熟悉的hello world程序吧。
Static void main(stirng[] args)
{
string str = “hello”;
Console.WriteLine(str);
}
为了一探究竟,一个好办法就是反汇编。
Debug,然后打开disassembly窗口,
下面是一些代码和对应的反汇编代码:
源代码:string str = “hello”;
汇编: mov eax , dword ptr ds:[02D52088h]
mov dword ptr [ebp-40h],eax
这两行汇编就是我要讨论的核心了。先解释一下。前面说过了,当我们声明一个变量的时候,系统就会创建一个指向这个变量的指针。现在,指向字符串”hello”的指针,就在地址02D52088h里面,我们把它取出来,然后保存到栈里面备用。具体在栈里面的位置,是从栈顶(ebp)向低位数40h的位置。
查看下寄存器,在地址02D52088h的位置,数出来32位,这个就是字符串的存放位置了哦。我这里是884dd101。好大一个地址啊。。。别被忽悠了,地址都是从低位到高位保存的,所以实际的地址是01d14d88,这个看起来正常多了。
查看内存(打开VS的内存窗口就可以了),跳转到地址01d14d88去看一下,果然找到了”hello”。不过,先别高兴太早,这里有点小问题。”hello”是找到了,可是并不在01d14d88这个位置,而是在它后面96位之后,也就是12个字节之后,才是hello。奇怪了,这96位共12个字节是什么东东呢?
试试找点线索来研究吧。
于是我又声明了几个string。记住string的内容要不一样哦,因为系统会进行优化,让具有相同内容的不同string变量指向同一个地方。再次查看,这回发现了很多有趣的东西。
先晒一下hello这个string的那12个字节的内容吧:
Ec 08 a4 66 06 00 00 00 05 00 00 00
再晒一下”aaaa”这个string的那12个字节的内容(这个”aaaa”是我声明的另外一个string):
Ec 08 a4 66 05 00 00 00 04 00 00 00
我想大家应该看出些眉目来了:
首先,前面的4个字节”ec 08 a4 66”是不变的。有理由认为这个就是string类型的标示符号。只要你是个string,最前面就要拿出4个字节来存这个数值。
然后是中间的4个字节和最后的4个字节。可以看出来,最后的4个字节,应该保存的是字符串里面字符的个数。比如hello是5,aaa就是4。那中间的4个字节呢?我怀疑也是跟个数有关系,而且看起来是跟字符个数是有关系的,至少这两个字符串都是字符个数加1。
长度加1。。。嗯,让我想起了c里面字符串用’/0’结尾,长度最少要比字符的个数多一个。为了搞清楚这个问题,让我们来仔细的看一下这块内存:
Ec 08 a4 66 06 00 00 00 :前面4个字节是字符串标示,后面4位是个数
05 00 00 00 68 00 65 00 :前面4个字节是字符个数,后面就是hello的内容,每个字符是16位的
6c 00 6c 00 6f 00 00 00 :后面的4个字节,前面的6f 00是字母‘o’,后面的00 00具体不详,看起来很像’/0’结尾哦
00 00 00 80 ec 08 a4 66 :前面有4个字节,不知道是什么,后面4个字节还是字符串标示,一个新的字符串开始了。
05 00 00 00 04 00 00 00
61 00 61 00 61 00 61 00
00 00 00 00 00 00 00 00
现在看来,一个字符串至少包括下面的内容:
4个字节的指针,4个字节的字符串标示,4个字节的字符个数,4个字节的跟字符相关的个数。
现在,再来关心下两个字符串之间的那个地带。再多声明几个字符串,对比后,终于看出了眉目。
那个”00 00 00 80”是个隔离带,用来分割两个字符串。然后就是还有一些不太规律的0。经过观察,发现每个字符串都会以00 00来结尾,这个应该就是传说中的’/0’了吧。但是有的地方可是00 00 00 00啊!多了4个0呢。
我猜想是这样的:系统要对齐代码,这样效率才高。假如我们有奇数个字符,算上最后的’/0’
正好偶数个,每个又是16位的,正好是32位的整数倍。可是如果有偶数个字符呢,就无法实现32位对齐了,因此就要在00 00后面再补上00 00。
于是,我们又有了下面的结论:
在两个字符串之间,有4个字节的隔离标示。然后呢,每个字符串后面都有2个字节的00 00。如果是偶数个字符的字符串,后面还要再多2个字节的00 00。另外刚才上面不太清楚的那个4个字节的字符个数,我现在怀疑是所谓的字符数组的长度,一般来说,就是字符个数加1了。
现在,可以小小的总结一下了:
假如我的程序里面有M个字符,那就要有:
4M个字节的指针,4M个字节的字符串标示,4M个字节的字符数组长度,4M个字节的字符个数,4(M-1)个字节的分隔符
再假设里面有一半的字符串长度是偶数,那就要多补2*M/2=M个字节的0,这样为了保存字符串,我们就要多用掉21M-4个字节。如果是100个字符串,就是大约2096B的额外内存。如果这些字符串的平均长度是100的话,那么我们的内存利用效率就是大约90%。
除此之外,是否还有其他的损失,我暂时也不太清楚。另外,为什么要有那个” 00 00 00 80”的分隔符,我也不清楚。反正现在仅仅是知其然而不知其所以然。就先研究到这里吧。要想搞得更明白,就要去研究下.net的编译器了。这个,水平有限,暂且搁置吧。