Recipe 16.1 Controlling Changes to Pointers Passedto Methods
Problem
You must pass a pointer variable in
to a method; however, you do not want to allow the called method to
change the address that the pointer passed in is pointing to. For
example, a developer wants to assume that after passing in a pointer
parameter to a method that that parameter is still pointing to the
same address when this method returns. If the called method were to
change what the pointer pointed to, bugs could be introduced into the
code.
In other cases, the converse may be true: the developer
wants to allow the address to be changed in the
method she passes the pointer to. Consider a developer who might
create a method that accepts two pointers and switches those pointers
by switching the memory locations to which each pointer points to,
rather than swapping the values each pointer points to.
Solution
You must decide whether to pass this pointer by value, by reference,
or as an out parameter. There are several methods
of passing arrays to methods. These methods include using or not
using the ref or out keywords
to define how the parameters are to be handled.
To make sure that a method does not
modify the pointer itself, you would pass the pointer by value, as
shown here:
unsafe
{
int num = 1;
int* numPtr = #
ModifyValue(numPtr);
// Continue using numPtr...
}
The method ModifyValue can still change the value
in the memory location to which the NumPtr pointer
is pointing to, but it cannot force NumPtr to
point to a different memory location after the method
ModifyValue returns.
To allow the method to modify the
pointer, pass it in by reference:
public unsafe void TestSwitchXY( )
{
int x = 100;
int y = 20;
int* ptrx = &x;
int* ptry = &y;
Console.WriteLine(*ptrx + "\t" + (int)ptrx);
Console.WriteLine(*ptry + "\t" + (int)ptry);
SwitchXY(ref ptrx, ref ptry);
Console.WriteLine(*ptrx + "\t" + (int)ptrx);
Console.WriteLine(*ptry + "\t" + (int)ptry);
}
public unsafe void SwitchXY(ref int* x, ref int* y)
{
int* temp = x;
x = y;
y = temp;
}
The SwitchXY method switches the values of the
x and y pointers so that they
point to the memory location originally pointed to by the other
parameter. In this case, you must pass the pointers in to the
SwitchXY method by reference
(ref). This action allows the
SwitchXY method to actually modify where a pointer
points to and to return this modified pointer.
Discussion
In safe code, passing a value type to a
method by value means that the value is passed
in, not the reference to that value. Therefore, the called method
cannot modify the value that the calling method's
reference points to; it can modify only the copy that it received.
It works the same way with unsafe code. When an unsafe pointer is
passed in to a method by value, the value of the pointer (which is a
memory location) cannot be modified; however, the value that this
pointer points to can be modified.
To examine the difference between passing a pointer by reference and
by value, we first need to set up a pointer to an integer:
int x = 5;
int* ptrx = &x;
Next, we write the method that attempts to modify the pointer
parameter:
private unsafe void CallByValue(int* x)
{
int newNum = 7;
x = &newNum;
}
Finally, we call the method and pass in ptrx to
this method:
CallByValue(ptrx);
If we examine the pointer variable ptrx before the
call to CallByValue, we see that it points to the
value 5. The called method
CallByValue changes the passed in parameter to
point to a different memory location. However, when the
CallByValue returns, the ptrx
pointer still points to the original memory location that contains
the value 5. The reason for this is that the
CallByValue method accepts the pointer
ptrx by value. This means that whatever value that
ptrx holds, a memory location in this case, it
cannot be modified, which is similar to when a reference type is
passed.
There are other times when we need
to allow a called method to modify the memory location that a pointer
points to. Passing a pointer by reference into a
method does this. This means that the called method may, in fact,
modify the memory location to which a pointer parameter points. To
see this, we again set up a pointer:
int x = 5;
int* ptrx = &x;
Next, we write the method that attempts to modify the parameter:
private unsafe void CallByRef(ref int* x)
{
int newNum = 7;
x = &newNum;
}
Finally, we call the method and pass the pointer by reference:
CallByRef(ref ptrx);
Now if we examine the value that the pointer ptrx
points to, before and after the call is made to
CallByRef, we see that it has indeed changed from
5 to 7. Not only this, but the
ptrx pointer is actually pointing to a different
memory location. Essentially, the ref keyword
allows the method CallByRef to modify the value
contained in the ptrx variable.
Let's consider the
use of the out or ref keywords
with pointers. A method that accepts a pointer as an
out or ref parameter is called
like this:
public unsafe void TestOut( )
{
int* ptrx;
CallUsingOut(out ptrx);
Console.WriteLine(*ptrx + "\t" + (int)ptrx);
}
The CallUsingOut method is written as follows:
public unsafe void CallUsingOut(out int* ptrx)
{
int x = 7;
ptrx = &x;
}
The ptrx variable is initially a
null pointer. After the call is made to the
CallUsingOut method, the ptrx
variable points to the value 7.
The code in this section of this recipe is meant to be as simple as
possible in order to explain the difference between passing a pointer
by value, by reference, and as an out parameter.
However, there is a serious flaw in the design of this example code
(the code in the Solution section does not contain this flaw). Take
the following code, for example:
public unsafe void TestOut( )
{
int* ptrx;
CallUsingOut(out ptrx);
Console.WriteLine(*ptrx);
SomeOtherMethod("Some Text");
Console.WriteLine(*ptrx);
}
The called method is written as follows:
public unsafe void CallUsingOut(out int* ptrx)
{
int temp = 7;
ptrx = &temp;
}
The problem is that the temp variable, pointed to
by the out parameter ptrx in
the CallUsingOut method, is in the stack frame of
the CallUsingOut method. The first call to
WriteLine displays the correct value
(7) for the pointer variable
ptrx since the CallUsingOut
method's stack frame is still intact. However, the
stack frame to the CallUsingOut method is promptly
overwritten when the call to SomeOtherMethod is
made, thereby causing the second call to WriteLine
to display garbage.
This mistake is easy to make, especially as the code gets more and
more complex. This error can also occur when returning a pointer from
a method as a return value. To solve this, you need to not assign
local variables in the scope of the method that are created on the
stack to the pointer since the value being pointed to can
"go away" once the scope is exited,
creating a dangling pointer.
 |
Be
very careful that you do not create dangling pointers (a pointer that
doesn't point at anything valid, such as by
assignging a pointer to memory that is collected before leaving the
function) when passing pointer parameters as ref
or out. This warning also applies to pointers used
as return values.
|
|
See Also
See the "Method Parameters,"
"out Parameter," and
"ref Parameter" topics in the MSDN
documentation.
|