System.Runtime.CompilerServices.Unsafe
クラスは一部で大変便利なのですが、ドキュメントを見ても動作が分かりにくいことが多いので、実際のILとC#へ逆コンパイルした結果を併記して簡易解説(メモ)します。
- 本質的でない属性は除去しています。
例:.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 )
- ニュアンスをつかみやすくするため、ILSpy version 6.0.0.5830-rc1 での逆コンパイル結果を併記しています。
- 逆コンパイル結果がおかしい場合がありますが、ILをC#的に表現できないものがあるので気にしません。
- ILコードをリポジトリから丸コピーしているので、ライセンスはThe MIT License (MIT)になります。
ILについて
-
.
で始まるものはディレクティブ -
&
はそのまま「参照」。C++/CLIでいうインテリア参照 -
!!T
は、T
という名前のメソッドのジェネリック型引数です。 -
native int
はIntPtr
です。 - スタックベースの仮想マシンです。引数はスタックに積まれています。この記事で頻出の命令をメモしておきます。
- 関数の引数は右側を先にスタックに積みます。
-
ldarg.N
で引数のN番目をスタックにロードします。 -
.maxstack N
で使用する最大のスタック数を宣言します。 -
cpblk
スタックから、コピー先、コピー元、バイト数をポップして、コピーする。コピー元とコピー先の領域とが重なっている場合,cpblkの動作は未規定。
- 詳しくはWikipediaのリストか PDFを見てください。
用語 | この記事での使い方 |
---|---|
ref |
IL的にはマネージドポインターですが、長いのでC#のref として表記します。 |
ポインター | 「マネージドポインター」以外は、アンマネージドポインターとして用います。 |
オブジェクト参照 | 参照型のインスタンス |
アライメントされた | アーキテクチャに依存するアドレスに配置された |
参照・ポインターの再解釈系
基本的にldarg.0
してret
する、つまり引数をそのまま戻り値の型として呼び出し元に返すだけ。
ジェネリック型のref
をvoidポインターとして解釈する
conv.u
で参照をポインターに変換しているっぽい。
public unsafe static void* AsPointer<T>(ref T value)
{
return Unsafe.AsPointer(ref value);
}
.method public hidebysig static void* AsPointer<T>(!!T& 'value') cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
conv.u
ret
} // end of method Unsafe::AsPointer
型チェックをスキップして、object
型を参照型のジェネリック型に再解釈する
純粋に再解釈するので誤ったキャストも許容される。
キャストの場合は、unbox.any !!T
命令が使用される。
public static T As<T>(object o) where T : class
{
return (T)o;
}
.method public hidebysig static !!T As<class T>(object o) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
ret
} // end of method Unsafe::As
あるジェネリック型のref
を、別のジェネリック型のref
に再解釈する
C++でいうとTFrom& source = ...; return (TTo&)source;
といったイメージ。
public static ref TTo As<TFrom, TTo>(ref TFrom source)
{
return ref Unsafe.As<TFrom, TTo>(ref source);
}
.method public hidebysig static !!TTo& As<TFrom, TTo>(!!TFrom& source) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
ret
} // end of method Unsafe::As
voidポインターをジェネリック型のref
に再解釈する
public unsafe static ref T AsRef<T>(void* source)
{
return ref *(T*)source;
}
.method public hidebysig static !!T& AsRef<T>(void* source) cil managed aggressiveinlining
{
// For .NET Core the roundtrip via a local is no longer needed see:
// https://github.com/dotnet/coreclr/issues/13341
// and
// https://github.com/dotnet/coreclr/pull/11218
# ifdef netcoreapp
.maxstack 1
ldarg.0
ret
# else
.locals (int32&)
.maxstack 1
ldarg.0
// Roundtrip via a local to avoid type mismatch on return that the JIT inliner chokes on.
stloc.0
ldloc.0
ret
# endif
} // end of method Unsafe::AsRef
readonly ref
をジェネリック型のref
に再解釈する
public static ref T AsRef<T>(in T source)
{
return ref source;
}
.param [1]
で仮引数1(1オリジン)に属性を適用している。
.method public hidebysig static !!T& AsRef<T>(!!T& source) cil managed aggressiveinlining
{
.param [1]
# ifdef netcoreapp
.custom instance void [CORE_ASSEMBLY]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
# else
.custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
# endif
.maxstack 1
ldarg.0
ret
} // end of method Unsafe::AsRef
コピー/読み取り/書き込み系
アラインメントされたvoidポインターから、読んだ値を返す
Cでいうと*((T*)source);
public unsafe static T Read<T>(void* source)
{
return *(T*)source;
}
.method public hidebysig static !!T Read<T>(void* source) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
ldobj !!T
ret
} // end of method Unsafe::Read
アラインメントされていないポインタから、読んだ値を返す
IA(x86, x64アーキテクチャ)では、恐らくUnalignedが付かない方を使用しても、(性能以外で)ペナルティはない。
逆コンパイル結果はアライメントされたものと同じだが、C#で表現できないからだろう。
public unsafe static T ReadUnaligned<T>(void* source)
{
return *(T*)source;
}
.method public hidebysig static !!T ReadUnaligned<T>(void* source) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
unaligned. 0x1
ldobj !!T
ret
} // end of method Unsafe::ReadUnaligned
アラインメントされていないbyte型のマネージドポインターから、読んだ値を返す
逆コンパイル結果はアライメントされたものと同じだが、C#で表現できないからだろう。
public unsafe static T ReadUnaligned<T>(ref byte source)
{
return *(T*)(&source);
}
.method public hidebysig static !!T ReadUnaligned<T>(uint8& source) cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
unaligned. 0x1
ldobj !!T
ret
} // end of method Unsafe::ReadUnaligned
アラインメントされたvoidポインターへ値を書き込む
Cでいうと*((T*)) = value;
public unsafe static void Write<T>(void* destination, T value)
{
*(T*)destination = value;
}
.method public hidebysig static void Write<T>(void* destination,
!!T 'value') cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
stobj !!T
ret
} // end of method Unsafe::Write
アラインメントされていないvoidポインターへ値を書き込む
Cでいうと*((__unaligned T*)) = value;
public unsafe static void WriteUnaligned<T>(void* destination, T value)
{
*(T*)destination = value;
}
.method public hidebysig static void WriteUnaligned<T>(void* destination,
!!T 'value') cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
unaligned. 0x01
stobj !!T
ret
} // end of method Unsafe::WriteUnaligned
アラインメントされていないbyte型のref
へ値を書き込む
public unsafe static void WriteUnaligned<T>(ref byte destination, T value)
{
*(T*)(&destination) = value;
}
.method public hidebysig static void WriteUnaligned<T>(uint8& destination,
!!T 'value') cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
unaligned. 0x01
stobj !!T
ret
} // end of method Unsafe::WriteUnaligned
ジェネリック型のref
からvoidポインターへコピーする
public unsafe static void Copy<T>(void* destination, ref T source)
{
*(T*)destination = source;
}
.method public hidebysig static void Copy<T>(void* destination,
!!T& source) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
ldobj !!T
stobj !!T
ret
} // end of method Unsafe::Copy
voidポインターからジェネリック型のref
へコピーする
public unsafe static void Copy<T>(ref T destination, void* source)
{
destination = *(T*)source;
}
.method public hidebysig static void Copy<T>(!!T& destination,
void* source) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
ldobj !!T
stobj !!T
ret
} // end of method Unsafe::Copy
voidポインターから指定されたバイト数分、voidポインターへコピーする(Cで云うmemcpy
)
public unsafe static void CopyBlock(void* destination, void* source, uint byteCount)
{
// IL cpblk instruction
Unsafe.CopyBlock(destination, source, byteCount);
}
.method public hidebysig static void CopyBlock(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
cpblk
ret
} // end of method Unsafe::CopyBlock
byte型のref
から指定されたバイト数分、byte型のref
へコピーする(Cで云うmemcpy
)
public static void CopyBlock(ref byte destination, ref byte source, uint byteCount)
{
// IL cpblk instruction
Unsafe.CopyBlock(ref destination, ref source, byteCount);
}
.method public hidebysig static void CopyBlock(uint8& destination, uint8& source, uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
cpblk
ret
} // end of method Unsafe::CopyBlock
アラインメントされていないvoidポインターから指定されたバイト数分、voidポインターへコピーする
public unsafe static void CopyBlockUnaligned(void* destination, void* source, uint byteCount)
{
// IL cpblk instruction
Unsafe.CopyBlockUnaligned(destination, source, byteCount);
}
.method public hidebysig static void CopyBlockUnaligned(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
unaligned. 0x1
cpblk
ret
} // end of method Unsafe::CopyBlockUnaligned
アラインメントされていないbyte型のref
から指定されたバイト数分、voidポインターへコピーする
public static void CopyBlockUnaligned(ref byte destination, ref byte source, uint byteCount)
{
// IL cpblk instruction
Unsafe.CopyBlockUnaligned(ref destination, ref source, byteCount);
}
.method public hidebysig static void CopyBlockUnaligned(uint8& destination, uint8& source, uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
unaligned. 0x1
cpblk
ret
} // end of method Unsafe::CopyBlockUnaligned
voidポインターへ指定されたバイト数分、バイト値を書き込む(Cで云うmemset
)
public unsafe static void InitBlock(void* startAddress, byte value, uint byteCount)
{
// IL initblk instruction
Unsafe.InitBlock(startAddress, value, byteCount);
}
.method public hidebysig static void InitBlock(void* startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
initblk
ret
} // end of method Unsafe::InitBlock
byte型のref
へ指定されたバイト数分、バイト値を書き込む(Cで云うmemset
)
public static void InitBlock(ref byte startAddress, byte value, uint byteCount)
{
// IL initblk instruction
Unsafe.InitBlock(ref startAddress, value, byteCount);
}
.method public hidebysig static void InitBlock(uint8& startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
initblk
ret
} // end of method Unsafe::InitBlock
アライメントされていないvoidポインターへ指定されたバイト数分、バイト値を書き込む(Cで云うmemset
)
public unsafe static void InitBlockUnaligned(void* startAddress, byte value, uint byteCount)
{
// IL initblk instruction
Unsafe.InitBlockUnaligned(startAddress, value, byteCount);
}
.method public hidebysig static void InitBlockUnaligned(void* startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
unaligned. 0x1
initblk
ret
} // end of method Unsafe::InitBlockUnaligned
アライメントされていないbyte型のref
へ指定されたバイト数分、バイト値を書き込む(Cで云うmemset
)
public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount)
{
// IL initblk instruction
Unsafe.InitBlockUnaligned(ref startAddress, value, byteCount);
}
.method public hidebysig static void InitBlockUnaligned(uint8& startAddress, uint8 'value', uint32 byteCount) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
unaligned. 0x1
initblk
ret
} // end of method Unsafe::InitBlockUnaligned
アドレス演算系
ジェネリック型のref
を要素数分の加算する
C++で云う(T&)(((T*)&source)+elementOffset)
public static ref T Add<T>(ref T source, int elementOffset)
{
return ref Unsafe.Add(ref source, elementOffset);
}
.method public hidebysig static !!T& Add<T>(!!T& source, int32 elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
conv.i
mul
add
ret
} // end of method Unsafe::Add
ジェネリック型のref
を要素数分加算する
こちらはnative int版
public static ref T Add<T>(ref T source, IntPtr elementOffset)
{
return ref Unsafe.Add(ref source, elementOffset);
}
.method public hidebysig static !!T& Add<T>(!!T& source, native int elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
mul
add
ret
} // end of method Unsafe::Add
voidポインターをジェネリック型として要素数分加算する
public unsafe static void* Add<T>(void* source, int elementOffset)
{
return (byte*)source + (long)elementOffset * (long)sizeof(T);
}
.method public hidebysig static void* Add<T>(void* source, int32 elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
conv.i
mul
add
ret
} // end of method Unsafe::Add
ジェネリック型のref
へ指定バイト数分加算する
public static ref T AddByteOffset<T>(ref T source, IntPtr byteOffset)
{
return ref Unsafe.AddByteOffset(ref source, byteOffset);
}
.method public hidebysig static !!T& AddByteOffset<T>(!!T& source, native int byteOffset) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
add
ret
} // end of method Unsafe::AddByteOffset
ジェネリック型のref
を要素数分減算する
ref *(((T*)&source) - elementOffset)
public static ref T Subtract<T>(ref T source, int elementOffset)
{
return ref Unsafe.Subtract(ref source, elementOffset);
}
.method public hidebysig static !!T& Subtract<T>(!!T& source, int32 elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
conv.i
mul
sub
ret
} // end of method Unsafe::Subtract
ジェネリック型のref
を要素数分加算する
こちらはnative int版
public static ref T Subtract<T>(ref T source, IntPtr elementOffset)
{
return ref Unsafe.Subtract(ref source, elementOffset);
}
.method public hidebysig static !!T& Subtract<T>(!!T& source, native int elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
mul
sub
ret
} // end of method Unsafe::Subtract
voidポインターをジェネリック型として要素数分減算する
ref *(((T*)source) - elementOffset)
public unsafe static void* Subtract<T>(void* source, int elementOffset)
{
return (byte*)source - (long)elementOffset * (long)sizeof(T);
}
.method public hidebysig static void* Subtract<T>(void* source, int32 elementOffset) cil managed aggressiveinlining
{
.maxstack 3
ldarg.0
ldarg.1
sizeof !!T
conv.i
mul
sub
ret
} // end of method Unsafe::Subtract
ジェネリック型のref
へ指定バイト数分減算する
ref *((T*)(((byte*)&source) - byteOffset))
public static ref T SubtractByteOffset<T>(ref T source, IntPtr byteOffset)
{
return ref Unsafe.SubtractByteOffset(ref source, byteOffset);
}
.method public hidebysig static !!T& SubtractByteOffset<T>(!!T& source, native int byteOffset) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
sub
ret
} // end of method Unsafe::SubtractByteOffset
ref
と同じ型の別のref
の差を求める
(byte*)(ref right) - (byte*)(ref left)
public static IntPtr ByteOffset<T>(ref T origin, ref T target)
{
return Unsafe.ByteOffset(ref target, ref origin);
}
.method public hidebysig static native int ByteOffset<T>(!!T& origin, !!T& target) cil managed aggressiveinlining
{
.maxstack 2
ldarg.1
ldarg.0
sub
ret
} // end of method Unsafe::ByteOffset
アドレス比較系
ref
が同じ値かどうかを比較する
public static bool AreSame<T>(ref T left, ref T right)
{
return (ref left) == (ref right);
}
.method public hidebysig static bool AreSame<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
ceq
ret
} // end of method Unsafe::AreSame
ref
が右辺より大きいか比較する
public static bool IsAddressGreaterThan<T>(ref T left, ref T right)
{
return (ref left) > (ref right);
}
.method public hidebysig static bool IsAddressGreaterThan<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
cgt.un
ret
} // end of method Unsafe::IsAddressGreaterThan
ref
が右辺より小さいか比較する
public static bool IsAddressLessThan<T>(ref T left, ref T right)
{
return (ref left) < (ref right);
}
.method public hidebysig static bool IsAddressLessThan<T>(!!T& left, !!T& right) cil managed aggressiveinlining
{
.maxstack 2
ldarg.0
ldarg.1
clt.un
ret
} // end of method Unsafe::IsAddressLessThan
他
マーシャリングを考慮しないサイズを求める
unsafeコンテキストでsizeof()
することと同じ。
Marshal.SizeOf()
はマーシャリングを考慮する。
個人的にはwhere T : unmanaged
をつけて欲しい。
public unsafe static int SizeOf<T>()
{
return sizeof(T);
}
.method public hidebysig static int32 SizeOf<T>() cil managed aggressiveinlining
{
.maxstack 1
sizeof !!T
ret
} // end of method Unsafe::SizeOf
.NET Core 3以降またはNuGet パッケージの最新(少なくとも4.7.1以降)
box化された構造体(オブジェクト参照)の内部のref
を取得する
C++/CLIで云うcli::interior_ptr
と等価。
public static ref T Unbox<T>(object box) where T : struct
{
return ref (T)box;
}
.method public hidebysig static !!T& Unbox<valuetype .ctor ([CORE_ASSEMBLY]System.ValueType) T> (object 'box') cil managed aggressiveinlining
{
.maxstack 1
ldarg.0
unbox !!T
ret
} // end of method Unsafe::Unbox
.NET 5以降
初期化(0クリア)をスキップして変数宣言する
多分CLRの対応が必要 https://github.com/dotnet/runtime/issues/29905
.method public hidebysig static void SkipInit<T> ([out] !!T& 'value') cil managed aggressiveinlining
{
.maxstack 0
ret
} // end of method Unsafe::SkipInit
個人的に欲しいメソッド
組み合わせれば実現するが、できれば直接欲しいと思っているメソッドの一覧。
- Cで云う
offsetof
- Cで云う
memmove
cpblk
に領域が重なっている場合の保証がないので。 -
readonly ref
への強制再解釈 - 全体的に
readonly ref
版のメソッド
必要無ければ、readonly
にしてほしいところだが、オーバーロードできないので多分厳しい。 - アライメント判定
- Overlaps判定(
Span<T>
にはあるけど。) - ジェネリック型
T where T : unmanaged
のマネージドポインターをポインターへの再解釈
public unsafe static T* ATypedsPointer<T>(ref T value) where T : unmanaged
{
return (T*)Unsafe.AsPointer(ref value);
}