1
unsafe class Program
{

    static void Main(string[] args)
    {
        int x;
        Thread t = new Thread(() => {  sum(12, 6, &x); }); // can't pass adrees of x
    }

    static unsafe void sum(int a ,int b,int* p)
    {
        *p = a + b;
    }

}

Throws the error:

Error CS1686: Local 'x' or its members cannot have their address taken and be used inside an anonymous method or lambda expression

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
ayoub_007
  • 53
  • 2
  • 8
  • you're not in an unsafe context where you call it i think, ie, try `unsafe static void Main(...` – mad.meesh Jan 10 '18 at 00:40
  • @mad.meesh: the entire class is `unsafe`. That's not the issue. The issue is the lifetime of the variable vs. the lifetime of the lambda in which it's used. The lambda may live longer than the variable, and thus when it's executed, would refer to an address that is no longer valid. – Peter Duniho Jan 10 '18 at 00:42
  • i use the unsafe in class to make sure all code is unsafe – ayoub_007 Jan 10 '18 at 00:42
  • @ayoub_007: it's not clear why you're using unsafe code here at all. Nothing in the example you posted actually needs unsafe code. C# has safer passing by-reference, via `ref` and `out`, and if you were to use those, the code would compile just fine (and you could get rid of the `unsafe` altogether). See my answer below. – Peter Duniho Jan 10 '18 at 00:56
  • actually i'm new in c# i didn't know there is " ref" and "out"keyword to work with pointer thx for help i appreciate , u r a lifesave – ayoub_007 Jan 10 '18 at 01:09
  • 1
    If you're using pointers in C#, you probably shouldn't be. –  Jan 10 '18 at 01:09
  • that why i love c++ – ayoub_007 Jan 10 '18 at 01:11
  • @ayoub_007: _"that why i love c++"_ -- not sure what you mean. I mean, nothing wrong with loving C++. But, in this particular example, C# offers a way to accomplish _exactly the same thing_, but without the opportunity to shoot yourself in the foot. For me, that's exactly why I love C# and am very glad to rarely have to use C++ anymore. I used to spend (waste) so much time just fiddling with the little details in C++ to make sure I didn't have silly typographical bugs in my code, while C# simply doesn't allow these and wide classes of other bugs to even exist. – Peter Duniho Jan 10 '18 at 01:31
  • of course, i'm so sorry i forgot to vote your answer – ayoub_007 Jan 10 '18 at 14:01

2 Answers2

1

The issue is the lifetime of the variable vs. the lifetime of the address taken in the lambda in which it's used. The compiler can ensure that the variable itself lives as long as the lambda (because it's stored in a separate hidden class, due to the capturing), but the address itself (which could be copied somewhere else after the fact) may live longer than the variable, and thus would refer to an address that is no longer valid.

Note that this contrasts with variables captured and used as ref or out parameters. For example:

class Program
{

    static void Main(string[] args)
    {
        int x;
        Thread t = new Thread(() => {  sum(12, 6, out x); });
    }

    static void sum(int a, int b, out int p)
    {
        p = a + b;
    }    
}

The above is allowed, because the captured variable will be moved from the stack to a separate class that holds it, and that class's lifetime can be ensured to be at least as long as the delegate that will use it.

The unsafe throws a monkey wrench into the works. Unlike ref and out, which have semantics that the compiler can enforce and always use safely, pointers can be stored in arbitrary ways, and the compiler has no way to ensure the lifetime of those pointers. So, even if it captures the x variable to a hidden class like it would in other situations, it still can't guarantee that class remains live for at least as long as the address.

Your specific example is theoretically safe, because the address isn't actually stored anywhere and is used immediately by the method that the anonymous methods calls. But the compiler can't guarantee that safety, and so prohibit any taking of the address of the captured variable.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Why is `unsafe int* ptrtox() { int x; return &x; }` okay then? – NetMage Jan 10 '18 at 01:20
  • @NetMage: Because the address is not being used in an anonymous method. I cannot tell you what led the C# language designers to this choice, not being one myself. What I _can_ tell you are [the rules](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/unsafe-code#pointers-in-expressions): _"A compile-time error occurs if E is not classified as a variable, if E is classified as a read-only local variable, **or if E denotes a moveable variable**"_. In the OP's code, the variable is moveable due to capturing and can't be used to take the address of. – Peter Duniho Jan 10 '18 at 01:28
  • I just wonder how capture of a (stack?) local variable is done if it isn't in a global GC object. – NetMage Jan 10 '18 at 17:14
  • @NetMage: that's just it. The local variable isn't on the stack if it's captured. The compiler rewrites the code, adding a hidden class to contain both the anonymous method and any variables that method captured. The instance of the class is referenced by the delegate created, and so lives as long as the delegate. The variable can't be GC'ed as long as the delegate is reachable. – Peter Duniho Jan 10 '18 at 17:28
  • Ah - that is for the lambda capture. What about for my sample function that returns a pointer to a local variable? – NetMage Jan 10 '18 at 18:56
  • @NetMage: what about it? That it's allowed doesn't make it safe. It's just that there is an explicit prohibition on taking the address of movable objects. In your example, `x` is not moveable, hence the prohibition doesn't apply. Keep in mind, the compiler isn't wasting any effort trying to figure out what you do with the address. It has no idea that all you've done is return it. All it cares about is whether it's allowed to take the address in that context, and it is. That you then return it, is immaterial with respect to the rules governing the use of `&`. – Peter Duniho Jan 11 '18 at 00:17
0

The memory location may have gone out of scope by the time the lambda is executed. Same reason you can't ref or out a parameter in a lambda. Lambda expressions capture the values of referenced variables at initialization time and use the captured values when they execute. Without the warning, you'd capture the address of x, then later when the lambda executes you would overwrite something else.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • Actually, it is not a problem to pass `ref` and `out` to methods used in anonymous methods. The compiler will always lift those variables out into a capturing class, so that their lifetimes are assured to be at least as long as that of the anonymous method delegate itself. – Peter Duniho Jan 10 '18 at 00:46
  • t think i should use fixed statement – ayoub_007 Jan 10 '18 at 00:50
  • _"The memory location may have gone out of scope by the time the lambda is executed"_ -- that statement is also inaccurate. Captured variables are stored in the same hidden class where the anonymous method is contained, and the lifetime of the variable can be ensured to last as long as the lambda itself. The problem is that the lambda can put the address somewhere else and the C# language chose to not allow this particular route of shooting yourself in the foot (`unsafe` still allows plenty of other ways). – Peter Duniho Jan 10 '18 at 01:01
  • @PeterDuniho I don't think it is the lambda that will put it somewhere else, but potentially GC if it compacts? – NetMage Jan 10 '18 at 17:13
  • @NetMage: see [my other comment](https://stackoverflow.com/questions/48178607/why-cannot-i-pass-the-address-of-a-variable-to-an-anonymous-function/48178838?noredirect=1#comment83366363_48178838). It is in fact the use of the anonymous method that moves the variable from the stack to a heap object. The issue isn't that the variable might go away, but that being in a heap object, it's movable, which isn't allowed by the spec for the `&` operator. – Peter Duniho Jan 10 '18 at 17:29