浅议字符串的内存分配

年前的面试经历(二)一文中提出了字符串的内存分配问题,引起园友们的广泛热议。我想程序员应该都是“刨根问底”栏目组的,不把问题刨个稀烂决不罢休。我这里也来简单地讨论一下。

问题的提出

那篇随笔中,面试官提出了这样一个问题:

string s1 = "a";

string s2 = "a" + "b";

各进行了几次内存分配。

分析

编译器优化

有一点是可以肯定的,即这两段代码所分配内存的次数是一样的。为什么呢?这是C#编译器所做的优化。在所有的字符串都是文本常量字符串时,编译器会在编译时将它们连接成一个字符串。也就是说在碰到诸如"a"+"b"这样的代码时,编译器会自动合并为"ab"。从IL代码中可以一看究竟:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       14 (0xe)
  .maxstack  1
  .locals init ([0] string s1,
           [1] string s2)
  IL_0000:  nop
  IL_0001:  ldstr      "a"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "ab"
  IL_000c:  stloc.1
  IL_000d:  ret
} // end of method Program::Main

为什么字符串的创建不是newobj而是ldstr呢?这其实也是CLR为了使字符串的操作更高效而进行的优化。编译器把文本常量存储在元数据中,ldstr就是从元数据中加载字符串

内存的分配

那么,是不是说从元数据加载字符串,就不会分配新的内存了呢?事实上并不是这样。

String类型是一个引用类型,这意味着它的实例必然分配在托管堆上。实际上ldstr进行了两项工作:

  • 在堆中分配内存
  • 对文本格式进行转换

而在线程栈上,还会分配一个对堆上内存地址的引用。

因此,我认为,string s1 = "a" 这样的语句,总共进行了2次内存分配。

考官问string s2 = "a" + "b" 进行了几次内存分配,主要是在考察应聘者是否知道编译器自动连接文本常量这样一个知识。如果回答3次,显然是对这个知识点不是很了解。不过我不是很清楚是否考察还会在栈上分配指针这个知识点。所以回答1次还是2次,都需要进一步地解释。

关于字符串的总结

  • String对象的创建是从元数据中加载文本常量,并对文本格式进行转换,然后在堆中分配内存地址,并将文本拷贝(?不是很确定)在该地址中。
  • String是一个引用类型,它是immutable的,因此表现为值类型的语义。
  • 字符串驻留(intern)机制使得创建相同内容的String对象指向相同的内存地址。字符串驻留是基于进程的。
  • String是跨应用程序域的,可以在不同应用程序域中访问同一个String对象。

欢迎大家在此展开讨论。

posted @ 2010-03-02 16:07  麒麟.NET  阅读(4044)  评论(17编辑  收藏  举报