我正在尝试理解我在某些C#代码中看到的一些行为,而不考虑这是否应该应该如何编写。基本上,请考虑以下代码:
<div class="row">
<div class="method-test">
<span class="glyphicon glyphicon-play"></span><h4>recognize() Method</h4>
<form id="recognizeForm">
Image (public URL or base64 data): <input type="text" class="image" name="Image">
Image (local file): <input type="file" class="image-upload" name="Image Upload">
Gallery Name: <input type="text" class="gallery_name" name="Gallery Name">
<input type="button" id="testRecognize" value="Test" />
</form>
</div>
</div>
在方法Init中,似乎在堆栈上分配的空间远远超过必要的空间。在任何实际的C#语句之前,该方法的反汇编窗口中会出现以下内容:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackTest
{
class MyClass
{
private int x;
public MyClass(int x)
{
this.x = x;
}
}
class DictClass
{
private Dictionary<Guid, MyClass> m_dict;
private Dictionary<int, MyClass> m_intDict;
public DictClass()
{
m_dict = new Dictionary<Guid, MyClass>();
m_intDict = new Dictionary<int, MyClass>();
Init(m_dict, m_intDict);
}
public void Init(
Dictionary<Guid, MyClass> dict,
Dictionary<int, MyClass> intDict)
{
int index = 0;
MyClass obj;
// BEGIN REPEATED_FRAGMENT
++index;
obj = new MyClass(index);
dict.Add(Guid.NewGuid(), obj);
intDict.Add(index, obj);
// END REPEATED_FRAGMENT
// Repeat REPEATED_FRAGMENT about 1400 times
}
public override string ToString()
{
return m_dict.Values.First().ToString();
}
}
class Program
{
static void Main(string[] args)
{
var dc = new DictClass();
Console.WriteLine(dc);
}
}
}
如果我正确读取它,它会为具有2个参数和2个局部变量的方法分配大约11 KB的堆栈空间,以及一些临时值。我的问题是:
同样,现在并不关心你是否应该以这种方式实际编写代码。只是好奇发生了什么。
答案 0 :(得分:3)
你是如何检查拆卸的?使用Visual Studio?还是像Windbg这样的低级调试器?
我问,因为查看整个反汇编方法,似乎很清楚,每次调用new MyClass(index)
和dict.Add(...)
时,堆栈空间都用于临时存储。例如,这是我在第一段看到的内容(注意粗体参数):
39: ++index; 07980082 inc dword ptr [ebp-0Ch] 40: obj = new MyClass(index); 07980085 mov ecx,2EA4E30h 0798008A call 02E930F4 0798008F mov dword ptr [ebp-10h],eax 07980092 mov ecx,dword ptr [ebp-10h] 07980095 mov edx,dword ptr [ebp-0Ch] 07980098 call dword ptr ds:[2EA4E2Ch] 0798009E mov eax,dword ptr [ebp-10h] 079800A1 mov dword ptr [ebp-4F38h],eax 41: dict.Add(Guid.NewGuid(), obj); 079800A7 lea ecx,[ebp-20h] 079800AA call 72D527F0 079800AF lea eax,[ebp-20h] 079800B2 sub esp,10h 079800B5 movq xmm0,mmword ptr [eax] 079800B9 movq mmword ptr [esp],xmm0 079800BE movq xmm0,mmword ptr [eax+8] 079800C3 movq mmword ptr [esp+8],xmm0 079800C9 mov ecx,dword ptr [ebp-4F34h] 079800CF mov edx,dword ptr [ebp-4F38h] 079800D5 cmp dword ptr [ecx],ecx 079800D7 call 72D2DD70 42: intDict.Add(index, obj); 079800DC push dword ptr [ebp-4F38h] 079800E2 mov ecx,dword ptr [ebp+8] 079800E5 mov edx,dword ptr [ebp-0Ch] 079800E8 cmp dword ptr [ecx],ecx 079800EA call 72CFF2F0
以下是我对第二部分的看法:
45: ++index; 079800EF inc dword ptr [ebp-0Ch] 46: obj = new MyClass(index); 079800F2 mov ecx,2EA4E30h 079800F7 call 02E930F4 079800FC mov dword ptr [ebp-24h],eax 079800FF mov ecx,dword ptr [ebp-24h] 07980102 mov edx,dword ptr [ebp-0Ch] 07980105 call dword ptr ds:[2EA4E2Ch] 0798010B mov eax,dword ptr [ebp-24h] 0798010E mov dword ptr [ebp-4F38h],eax 47: dict.Add(Guid.NewGuid(), obj); 07980114 lea ecx,[ebp-34h] 07980117 call 72D527F0 0798011C lea eax,[ebp-34h] 0798011F sub esp,10h 07980122 movq xmm0,mmword ptr [eax] 07980126 movq mmword ptr [esp],xmm0 0798012B movq xmm0,mmword ptr [eax+8] 07980130 movq mmword ptr [esp+8],xmm0 07980136 mov ecx,dword ptr [ebp-4F34h] 0798013C mov edx,dword ptr [ebp-4F38h] 07980142 cmp dword ptr [ecx],ecx 07980144 call 72D2DD70 48: intDict.Add(index, obj); 07980149 push dword ptr [ebp-4F38h] 0798014F mov ecx,dword ptr [ebp+8] 07980152 mov edx,dword ptr [ebp-0Ch] 07980155 cmp dword ptr [ecx],ecx 07980157 call 72CFF2F0
换句话说,第一段中使用了堆栈广告位[ebp-10h]
和[ebp-20h]
,而第二段中使用了广告位[ebp-24h]
和[ebp-34h]
。
自从我不得不担心本机编译器将代码转化为什么以来,已经有很长一段时间了。上次我不得不调试堆栈使用问题几乎是在二十年前。但是,似乎很清楚,编译器已经决定由于某种原因,它需要每个调用的新临时变量,因此需要大量的分配。
可能在完全优化的构建中,即不在Visual Studio的调试器下运行(当连接到进程本身时,即使对于Release版本也可以抑制优化),编译器能够优化那些堆栈槽,将它们组合成为每个调用重用的单个变量。因此我的问题是关于你如何观察代码。
如果您在JIT编译器中看到输出,即使在没有附加Visual Studio调试器的情况下编译代码,那么我也没有很好地解释为什么编译器不为每个调用共享堆栈槽。虽然,一个大的方法可能会导致优化器放弃,这足以解释。 :)
当然,正如你已经提到的那样,这实际上是一个完全没有问题的。这不是一个理智的人如何编写代码,所以疯狂的后果纯粹是学术性的。