我在查看BitConverter类的源代码时,突然发现大量使用 fixed关键字.
Reference Source
如下代码,将Int16转化为两个字节【C#中,是低字节在前的】
public static readonly bool IsLittleEndian = true;
// Converts a short into an array of bytes with length
// two.
[System.Security.SecuritySafeCritical] // auto-generated
public unsafe static byte[] GetBytes(short value)
{
Contract.Ensures(Contract.Result<byte[]>() != null);
Contract.Ensures(Contract.Result<byte[]>().Length == 2);
byte[] bytes = new byte[2];
fixed(byte* b = bytes)
*((short*)b) = value;
return bytes;
}
将4个字节转化为一个Int32数
// Converts an array of bytes into an int.
[System.Security.SecuritySafeCritical] // auto-generated
public static unsafe int ToInt32 (byte[] value, int startIndex) {
if( value == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
}
if ((uint) startIndex >= value.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
}
if (startIndex > value.Length -4) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
Contract.EndContractBlock();
fixed( byte * pbyte = &value[startIndex]) {
if( startIndex % 4 == 0) { // data is aligned
return *((int *) pbyte);
}
else {
if( IsLittleEndian) {
return (*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24);
}
else {
return (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
}
}
}
}
下面对fixed关键字进行说明 以及使用场景
参考微软官方文档:
fixed 语句 - C# 参考 | Microsoft Docs
fixed 语句可防止垃圾回收器重新定位可移动的变量。 fixed 语句仅允许存在于fixed 上下文中。 还可以使用 fixed 关键字创建fixed 。一般fixed关键字都用在unsafe【不安全】的环境中
fixed 语句将为托管变量设置一个指针,并在该语句的执行过程中“单边锁定”该变量。 仅可在 fixed 上下文中使用指向可移动托管变量的指针。 如果没有 fixed 上下文,垃圾回收可能会不可预测地重定位变量。 C# 编译器只允许将指针分配给 fixed 语句中的托管变量。
使用fixed关键字 可以提升运算的性能,
? 不能使用 fixed 语句来获取已固定的表达式的地址,因此fixed不能获取结构struct的地址
可以通过使用一个数组、字符串、固定大小的缓冲区或变量的地址来初始化指针。
执行该fixed语句中的代码之后,任何固定的变量都将被解锁并受垃圾回收的约束。 因此,请勿指向?fixed ?语句之外的那些变量。 在?fixed ?语句中声明的变量的作用域为该语句,使此操作更容易:
在?fixed ?语句中初始化的指针为只读变量。 如果想要修改指针值,必须声明第二个指针变量,并修改它。 不能修改在?fixed ?语句中声明的变量:
可以在堆栈上分配内存,在这种情况下,内存不受垃圾回收的约束,因此不需要固定。 为此,请使用?表达式。
使用VisualStudio2022新建控制台应用程序FixedDemo,选择.net 5.0
右键FixedDemo项目,选择属性,勾选“允许使用unsafe关键字编译的代码”。
?源程序如下:
using System;
namespace FixedDemo
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine($"测试fixed关键字,将数组的一部分转化为Span");
FixedSpanExample();
Console.WriteLine($"测试使用指针修改存储的值");
ModifyFixedStorage();
Console.WriteLine();
}
/// <summary>
/// fixed关键字示例
/// </summary>
unsafe private static void FixedSpanExample()
{
int[] PascalsTriangle = {
1,
1, 1,
1, 2, 1,
1, 3, 3, 1,
1, 4, 6, 4, 1,
1, 5, 10, 10, 5, 1
};
Span<int> RowFive = new Span<int>(PascalsTriangle, 10, 5);
fixed (int* ptrToRow = RowFive)
{
// 计算数字之和 1,4,6,4,1
int sum = 0;
for (int i = 0; i < RowFive.Length; i++)
{
sum += *(ptrToRow + i);
}
Console.WriteLine(sum);
}
}
/// <summary>
/// 使用fixed指针修改内存的值,不能使用fixed关键字获取结构的地址
/// </summary>
unsafe private static void ModifyFixedStorage()
{
// Variable pt is a managed variable, subject to garbage collection.
Coordinate coordinate = new Coordinate()
{
X = 100,
Y = 200
};
Console.WriteLine($"初始值:X={coordinate.X},Y={coordinate.Y}");
// Using fixed allows the address of pt members to be taken,
// and "pins" pt so that it is not relocated.
fixed (int* p = &coordinate.X, q = &coordinate.Y)
{
*p = 12345;
*q = 67890;
}
Console.WriteLine($"修改后的值:X={coordinate.X},Y={coordinate.Y}");
}
}
/// <summary>
/// 坐标,fixed关键字不能获取已固定的表达式的地址
/// 因此fixed不能获取结构struct的地址
/// </summary>
class Coordinate
{
public int X;
public int Y;
public int Z;
}
}
程序运行如图:
?
|