Home > Blockchain >  What's the differences between out and int* c#
What's the differences between out and int* c#

Time:12-09

As far as I read sending out keyword in c#, will change the parameter. So what's the different between those two cases:

  1. using out:

    helper(out param){...}
    
     func(){
    
     int param = 0;
    
     helper(out param)}
    
  2. using pointer:

    helper(int* param){..}  
    
     func(){
    
     int param = 0;
    
     helper(&param)}
    

Are they same ? Tnx.

CodePudding user response:

The use cases are not same as the keywords name suggests.

The out is a keyword in C# which is used for the passing the arguments to methods as a reference type. It is generally used when a method returns multiple values. The out parameter does not pass the property.

The pointer in C# which is used for the passing the arguments by a reference. Or we can say that if any changes made in this argument in the method will pointer reflect in that variable when the control return to the calling method.

Pointers are typically there to interop with C and aren't to be used in day to day C# coding. We can use ref type.

CodePudding user response:

These two things are conceptually similar. The key difference here is:

  • int* is an unmanaged pointer
  • ref int is a managed pointer
  • out int is really just ref int with some extra compiler guard rails around definite assignment

So: what is the difference between a managed and unmanaged pointer? The key differences:

  • the GC can correctly track managed pointers
  • which means you don't need to pin/fix some things before you can use them
  • and therefore you don't need to use unsafe
  • and it is more verifiable
  • and more people understand the code

Conversely:

  • you can store an unmanaged pointer in a field on a type (managed pointers can only be held on the stack)
  • but if that unmanaged pointer talks to something that has moved/gone away: BOOM

So: in any scenario where you can use a managed pointer (ref or out), in place of an unamnaged pointer: you should. Honestly, very few C# developers fully understand the nuances of managed pointers, but even fewer fully understand the nuances of unamnaged pointers.

CodePudding user response:

On the higher level of course there are differences related to how unsafe code is implemented, such that you can only take pointers to primitive types vs. with the out keyword any type can be specified as an argument.

But it is interesting to see how the two differ down in the lower level assembly code.

Consider the minimum skeleton code.

static class Program
{
    static unsafe void Main(string[] args)
    {
        int x;
        SetArg(out x);
        Console.WriteLine(x);

        int y;
        SetArg(&y);
        Console.WriteLine(y);
    }

    static void SetArg(out int x)
    {
        x = 10;
    }

    static unsafe void SetArg(int* ptr)
    {
        *ptr = 10;
    }
}

and the resulting assembly code which is inlined

            SetArg(out x);
00007FFD4EB80890  int         3  
00007FFD4EB80891  sub         esp,28h  
00007FFD4EB80894  xor         eax,eax  
00007FFD4EB80896  mov         dword ptr [rsp 24h],eax  
            Console.WriteLine(x);
00007FFD4EB8089A  mov         ecx,0Ah                           ! case a)
00007FFD4EB8089F  call        00007FFDAA4E2890  

            int y;
            SetArg(&y);
00007FFD4EB808A4  lea         rcx,[rsp 24h]  
00007FFD4EB808A9  mov         dword ptr [rcx],0Ah               ! case b)
            Console.WriteLine(y);
00007FFD4EB808AF  mov         ecx,dword ptr [rsp 24h]  
00007FFD4EB808B3  call        00007FFDAA4E2890  
00007FFD4EB808B8  nop  
00007FFD4EB808B9  add         rsp,28h  
00007FFD4EB808BD  ret  

In the first case with the out statement, the value 10 = 0Ah is written directly to a register ecx before being written to the console by the Writeline() function. This is super optimized as no memory is utilized in this operation. This is why it is not recommended to micro optimize your code, and just convey intent with the ref/out keywords and let the compiler do its job.

In the second case with the int* argument, the value 10 = 0Ah is written to a memory address on the stack corresponding to the function argument and then loaded up to ecx from that address before calling Writeline(). Here the code is not only unsafe but also less optimized as the programmer forced the compiler to store the intermediate value in the stack before usage.

Note than an alternate definition of SetArg() is

    static unsafe void SetArg(int* ptr)
    {
        ptr[0] = 10;  // same as ptr* = 10
    }

but this highlights another potential problem. Buffer overrun, or out of bounds memory access, because if you had written

ptr[-1] = 10;

or anything else other than ptr[0] which is the intended memory location, then you have corrupted the process memory causing havok.

  •  Tags:  
  • c#
  • Related