As far as I read sending out keyword in c#, will change the parameter. So what's the different between those two cases:
using out:
helper(out param){...} func(){ int param = 0; helper(out param)}
using pointer:
helper(int* param){..} func(){ int param = 0; helper(¶m)}
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 pointerref int
is a managed pointerout int
is really justref 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.