gpt4 book ai didi

c# - 当本地 stackalloc 上的数据时,匿名委托(delegate)不会在每次迭代中使用新本地

转载 作者:太空狗 更新时间:2023-10-30 00:47:50 26 4
gpt4 key购买 nike

在 C# 中使用匿名 delegate 时,CLR 将在堆上为使用过的变量生成局部副本(例如,当前作用域中的变量)。对于当前作用域的每个已声明变量,这样的局部变量将被放入堆中。

你可以在这个例子中看到这个行为:

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
ThreadPool.QueueUserWorkItem(delegate { execute(i); });

Thread.Sleep(1000);

Console.WriteLine();

for (int i = 0; i < 5; i++)
{
int j = i;

ThreadPool.QueueUserWorkItem(delegate { execute(j); });
}

Thread.Sleep(1000);
}

static void execute(int number)
{
Console.WriteLine(" * NUM=" + number.ToString());
}
}

此程序的输出是(最后 5 个条目的顺序可能有所不同,而第一个条目的顺序也可能小于 5。):

 * NUM=5
* NUM=5
* NUM=5
* NUM=5
* NUM=5

* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4

在方法中调用时,C# 应始终生成本地的新副本。这在本示例中按预期工作:

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
call(i);

Thread.Sleep(1000);
}

static void call(int number)
{
ThreadPool.QueueUserWorkItem(delegate { execute(number); });
}

static void execute(int number)
{
Console.WriteLine(" * NUM=" + number.ToString());
}
}

输出:

 * NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4

这是有问题的情况:但是,当将变量分配给 stackalloc 保留区域时,它工作:

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
call(i);

Thread.Sleep(1000);
}

static unsafe void call(int number)
{
int* ints = stackalloc int[64];

ints[32] = number;

ThreadPool.QueueUserWorkItem(delegate { execute(ints[32]); });
}

static void execute(int number)
{
Console.WriteLine(" * NUM=" + number.ToString());
}
}

输出:

 * NUM=4
* NUM=4
* NUM=4
* NUM=4
* NUM=4

当使用常规局部变量时 - 只需替换上面示例中的 call 方法:

static void call(int number)
{
int j = number;

ThreadPool.QueueUserWorkItem(delegate { execute(j); });
}

输出:

 * NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4

这种情况让我不相信 C# 中的匿名 delegate - 因为我不明白 C# 到底什么时候不会搞砸我对匿名 delegate 的调用。

我的问题:为什么 C# 不跟踪关于匿名 delegatestackalloc 空间?我知道 C# 不保留追踪。我想知道为什么它不跟踪,如果它使用常规变量的话。

我将 .NET Core 2.1 与 C# 7.3 结合使用,包括用于这些示例的 /unsafe 开关。

最佳答案

问题是您正在捕获一个指针。该指针指向由 call 在堆栈上分配的内存 - 即使在方法返回后指针保持对它的引用,这从根本上来说是个坏消息。那时您进入了未定义的领域 - 无法保证以后该内存中会有什么。

每个 stackalloc 确实 分别出现 - 你得到的五个指针都是独立的,但它们发生指向同一 block 内存,因为每个都是单独的 stackalloc 执行的结果当堆栈指针处于相同的值开始时。您仍然可以使用该内存,因为它在进程中仍然是有效内存,但这样做安全,因为要知道那里会发生什么。

ints 变量被“正确地”复制到由编译器生成的类中,但是变量的 指的是位于堆栈上的内存调用 call 方法的时间。当我运行代码时,我得到了“无论 Thread.Sleep 的参数是什么。C# 编译器正在捕获变量,这与“捕获堆栈的全部内容”。

您不需要完全避免委托(delegate) - 您只需要避免将委托(delegate)与不安全的代码和堆栈分配混合在一起。

根本不用任何匿名函数也能看到这个问题:

using System;
using System.Threading;

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Call(i);
}

Thread.Sleep(999);
}

static unsafe void Call(int number)
{
Helper helper = new Helper();
int* tmp = stackalloc int[64];
helper.ints = tmp;
helper.ints[32] = number;
ThreadPool.QueueUserWorkItem(helper.Method);
}

static void Execute(int number)
{
Console.WriteLine(" * NUM=" + number.ToString());
}

unsafe class Helper
{
public int* ints;

public void Method(object state)
{
Execute(ints[32]);
}
}
}

无需使用任何委托(delegate),您就可以很容易地看到它,但是做同样的事情“堆栈分配一些内存,并在该堆栈消失后使用指向它的指针”:

using System;

class Program
{
unsafe static void Main(string[] args)
{
int*[] pointers = new int*[5];
for (int i = 0; i < 5; i++)
{
pointers[i] = Call(i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(pointers[i][32]);
}
}

unsafe static int* Call(int number)
{
int* ints = stackalloc int[64];
ints[32] = number;
return ints;
}
}

关于c# - 当本地 stackalloc 上的数据时,匿名委托(delegate)不会在每次迭代中使用新本地,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54414317/

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