gpt4 book ai didi

c# - 局部函数中声明的值类型变量是否是堆栈分配的?

转载 作者:行者123 更新时间:2023-12-03 07:19:55 25 4
gpt4 key购买 nike

我正在阅读最近介绍的本地函数,并开始思考这个问题。 Afaik lambda 中值类型的局部变量是在堆中分配的。此外,在捕获值类型时,本地函数比 lambda 更有优势,在这种情况下不需要额外的堆分配。我仍然不清楚:

  1. 在局部函数中声明的局部值类型变量是否在堆栈上分配?
  2. 在“父”函数中声明并在本地函数中捕获的值类型变量怎么样?

(前提是父级本身不是匿名的)。

编辑:

int ParentFunction ()
{
int parentVarLambda = 0;
int parentVarLocal = 0;

Func<int> lamdaFuncion = () => parentVarLambda + 1;

int a = lamdaFuncion();
int b = LocalFunction();

return a + b;

int LocalFunction()
{
int localFuncVar = 1;
return parentVarLocal += localFuncVar ;
}
}

parentVarLambda、parentVarLocal 和 localFuncVar 将分配到哪里?

最佳答案

它们都不是堆分配的,除非发生其他情况(特别是如果编译器不能保证本地函数捕获的变量的生命周期不超过父方法的生命周期,例如,如果delegate 指本地函数,或者本地函数包含 yield returnawait 语句)。

假设您有:

public void M(int i) {
Inner(i + 1);

void Inner(int x)
{
int j = x + i;
Console.WriteLine(j);
}
}

使用精彩SharpLab ,我们可以看到它被编译为:

[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <>c__DisplayClass0_0
{
public int i;
}

public void M(int i)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = default(<>c__DisplayClass0_0);
<>c__DisplayClass0_.i = i;
<M>g__Inner|0_0(<>c__DisplayClass0_.i + 1, ref <>c__DisplayClass0_);
}

[CompilerGenerated]
internal static void <M>g__Inner|0_0(int x, ref <>c__DisplayClass0_0 P_1)
{
Console.WriteLine(x + P_1.i);
}

因此编译器采用了我们的内部函数,并将其重写为静态方法。内部函数的参数仍然作为静态方法的参数。内部函数捕获的内容最终作为编译器生成的结构上的字段,该结构由 ref 传递(以避免复制,以便在静态方法中对其所做的更改反射(reflect)在调用方法中)。

在该内部函数中分配的结构将在静态方法中进行相同的分配 - 即在堆栈上。

<小时/>

现在让我们将其与使用委托(delegate)的等效代码进行比较:

public void M(int i) {
Action<int> inner = x =>
{
int j = x + i;
Console.WriteLine(j);
};

inner(i + 1);
}

这个gets compiled to :

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int i;

internal void <M>b__0(int x)
{
Console.WriteLine(x + i);
}
}

public void M(int i)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.i = i;
new Action<int>(<>c__DisplayClass0_.<M>b__0)(<>c__DisplayClass0_.i + 1);
}

在这里我们可以看到区别。编译器生成了一个新的,它具有保存委托(delegate)捕获的变量的字段,并且有一个包含委托(delegate)主体的方法。它必须使用类,而不是通过引用传递的结构。

要理解原因,请考虑一下这样一个事实:您的代码可以传递委托(delegate) - 它可以将其存储在字段中,或返回它,或将其传递给另一个方法。在这种情况下,它不仅被其父级同步调用(本地函数必须如此),而且还必须携带它捕获的变量。

<小时/>

请注意,如果我们创建引用本地函数的委托(delegate),则会发生类似的情况:

public void M(int i) {
void Inner(int x)
{
int j = x + i;
Console.WriteLine(j);
}

Action<int> inner = Inner;
inner(i + 1);
}

这个gets compiled to the same as before :

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int i;

internal void <M>g__Inner|0(int x)
{
Console.WriteLine(x + i);
}
}

public void M(int i)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.i = i;
new Action<int>(<>c__DisplayClass0_.<M>g__Inner|0)(<>c__DisplayClass0_.i + 1);
}

在这里,编译器发现它无论如何都需要创建委托(delegate),因此它生成与上一个示例相同的代码。

请注意,在其他情况下,编译器在调用本地函数时必须执行堆分配,例如本地函数必须可恢复,因为它包含 yield returnawait声明。

<小时/>

要解决您编辑中的具体示例:

int ParentFunction ()
{
int parentVarLambda = 0;
int parentVarLocal = 0;

Func<int> lamdaFuncion = () => parentVarLambda + 1;

int a = lamdaFuncion();
int b = LocalFunction();

return a + b;

int LocalFunction()
{
int localVar = 1;
return parentVarLocal += localVar;
}
}

我们可以再次put this into SharpLab, and get :

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int parentVarLambda;

public int parentVarLocal;

internal int <ParentFunction>b__0()
{
return parentVarLambda + 1;
}

internal int <ParentFunction>g__LocalFunction|1()
{
int num = 1;
return parentVarLocal += num;
}
}

private int ParentFunction()
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.parentVarLambda = 0;
<>c__DisplayClass0_.parentVarLocal = 0;
int num = new Func<int>(<>c__DisplayClass0_.<ParentFunction>b__0)();
int num2 = <>c__DisplayClass0_.<ParentFunction>g__LocalFunction|1();
return num + num2;
}

请注意,编译器意识到它无论如何都必须为委托(delegate)创建生成类的新实例,因此它只是选择以相同的方式处理本地函数,而无需额外成本。在这种情况下并没有多大区别,但是当委托(delegate)和本地函数捕获相同的变量时需要这种技术 - 它们需要被提升到相同的生成类中。

因此,parentVarLambdaparentVarLocal分配在同一个编译器生成的类上,并且 localFuncVar刚刚得到优化(但会在 <ParentFunction>g__LocalFunction|1() 中的堆栈上分配)。

关于c# - 局部函数中声明的值类型变量是否是堆栈分配的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55744208/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com