I have a c# program that converts/re-interpret unmanaged type to ulong
on x64 machine:
public static unsafe ulong ToUInt64<T>(T value) where T : unmanaged
{
if(sizeof(T) > sizeof(ulong))
throw new InvalidOperationException("Can't convert this type to UInt64");
return *(ulong*)&value;
}
Usage:
int a = 123;
ulong b = ToUInt64(a);
Console.WriteLine(b); // returns 123 as expected.
However, I'm not sure if this is 100% safe.
Namely, will the runtime always allocate 8 bytes and zero the memory if a variable have a smaller size? Can I get AccessViolationException
or read some garbage memory into the ulong under some situation?
CodePudding user response:
I think this is still based on the runtime.
At first I cannot ensure this example is reproductive on every machine.
static void Main()
{
var j = Test(0x12345678);
ulong b = ToUInt64((byte)123);
Console.WriteLine(b); // returns 123 as expected.
Console.WriteLine(j);
}
public unsafe static short Test(int value)
{
return (short)(value - 1);
}
public static unsafe ulong ToUInt64<T>(T value) where T : unmanaged
{
return *(ulong*)&value;
}
When I debugged (F5) the program (in release mode), I got the following result. You can see the first value is unexpected.
The gernerated assembly is:
00007FFDD6148D64 mov dword ptr [rsp 24h],7Bh
00007FFDD6148D6C mov rcx,qword ptr [rsp 24h]
The value 123 (0x7B) is moved as a dword ptr, the returned value is moved as a qword ptr, so unless the runtime always clears the parameter stack, you cannot guarantee the final value will be pure.
CodePudding user response:
Turns out I neglected two things at first.
- The value is copied on function call, so it'll be different than "re-interpreting" the variable outright.
- If I allocate an array and re-interpret it in scope, it'll always fail. Since it reads adjacent unwanted memory:
int* a = stackalloc int[2];
a[0] = a[1] = 123;
Console.WriteLine(*(ulong*)&a[0]); // 528280977531
Even if I wraps it into a function, it may still fail sometimes. The result vary depending on Debug/Release configuration and other codes. Those may due to the code optimization and what's being written to the call stack (accidentally read some instructions?)
The 100% safe approach is alwasy allocate a new zeroed variable then copy the data:
public static unsafe ulong ToUInt64<T>(T value) where T : unmanaged
{
if (sizeof(T) > sizeof(TResult))
throw new InvalidCastException("Can't cast from data type with larger size");
ulong result = 0;
*(T*)&result = value;
return result;
}
A more generic implementation:
public static unsafe TResult UnmanagedCast<T,TResult>(T value) where T : unmanaged where TResult : unmanaged
{
if (sizeof(T) > sizeof(TResult))
throw new InvalidCastException("Can't cast from data type with larger size");
TResult result = default;
*(T*)&result = value;
return result;
}
usage:
UnmanagedCast<int,ulong>(123)