65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 编写不安全代码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (49投票s)

2001 年 5 月 8 日

CPOL

5分钟阅读

viewsIcon

238552

downloadIcon

1604

一个简单的教程,展示了如何使用 C# 编写不安全代码

引言

在不安全代码(或称非托管代码)中,可以声明和使用指针。但问题是我们为什么要编写非托管代码?如果我们想编写与操作系统交互的代码,或者想访问内存映射设备,或者想实现一个对时间要求很高的算法,那么使用指针可以带来很多优势。

但是,使用指针也有一些缺点。如果在编译时将指针选择为 32 位量,那么代码将仅限于 4GB 的地址空间,即使它运行在 64 位机器上。如果在编译时将指针选择为 64 位,那么代码将无法在 32 位机器上运行。

在 C# 中,我们可以使用 `unsafe` 修饰符来编写不安全代码。所有不安全代码都必须用 `unsafe` 修饰符明确标记。编写不安全代码就像在 C# 程序中编写 C 代码一样。

现在让我们看看第一个程序

程序 1

using System;

class MyClass {
	public static void Main() {
		int iData = 10;
		int* pData = &iData;
		Console.WriteLine("Data is " + iData);
		Console.WriteLine("Address is " + (int)pData );
	}
}

在这个程序中,我使用了一个指针。现在编译这个程序。编译器会给出错误

Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

um1.cs(6,8): error CS0214: Pointers may only be used in an unsafe context
um1.cs(8,27): error CS0214: Pointers may only be used in an unsafe context

现在让我们稍微修改一下程序,并在函数中添加 `unsafe` 修饰符。

程序 2

using System;

class MyClass {
	public unsafe static void Main() {
		int iData = 10;
		int* pData = &iData;
		Console.WriteLine("Data is " + iData);
		Console.WriteLine("Address is " + (int)pData );
	}
}

在这个程序中,`Main()` 函数被定义为 `unsafe`,所以我们可以在这个函数中使用指针。程序的输出是

Data is 10
Address is 1244316

不一定非要把 `unsafe` 修饰符定义在函数上。我们可以定义一个不安全的代码块。让我们再稍微修改一下程序。

程序 3

using System;

class MyClass {
	public static void Main() {
		unsafe {
			int iData = 10;
			int* pData = &iData;
			Console.WriteLine("Data is " + iData);
			Console.WriteLine("Address is " + (int)pData );
		}
	}
}

在这个程序中,一个代码块是用 `unsafe` 修饰符定义的。所以我们可以在该代码中使用指针。这个程序的输出与上一个程序相同。

现在让我们稍微修改一下程序,以便从指针中获取一个值。

程序 4

using System;

class MyClass {
	public static void Main() {
		unsafe {
			int iData = 10;
			int* pData = &iData;
			Console.WriteLine("Data is " + iData);
			Console.WriteLine("Data is " + pData->ToString() );
			Console.WriteLine("Address is " + (int)pData );
		}
	}
}

我们可以使用 `ToString()` 成员函数从指针中获取数据。让我们稍微修改一下程序,看看它的行为。

程序 5

using System;

class MyClass {
	public static void Main() {
		testFun();
	}

	public static unsafe void testFun() {
		int iData = 10;
		int* pData = &iData;
		Console.WriteLine("Data is " + iData);
		Console.WriteLine("Address is " + (int)pData );
	}
}

在这个程序中,一个带有 `unsafe` 修饰符的函数从一个普通函数中被调用。这个程序表明托管代码可以调用非托管函数。程序的输出与上一个程序相同。

现在稍微修改一下程序,在另一个类中创建一个不安全函数。

程序 6

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.testFun();
	}
}

class TestClass {
	public unsafe void testFun() {
		int iData = 10;
		int* pData = &iData;
		Console.WriteLine("Data is " + iData);
		Console.WriteLine("Address is " + (int)pData );
	}
}

程序的输出与上一个相同。

现在尝试将指针作为参数传递。我们来看看这个程序。

程序 7

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.testFun();
	}
}

class TestClass {
	public unsafe void testFun() {
		int x = 10;
		int y = 20;
		Console.WriteLine("Before swap x = " + x + " y= " + y);
		swap(&x, &y);
		Console.WriteLine("After swap x = " + x + " y= " + y);
	}

	public unsafe void swap(int* p_x, int *p_y) {
		int temp = *p_x;
		*p_x = *p_y;
		*p_y = temp;
	}
}

在这个程序中,不安全函数 `testFun()` 调用经典的 `swap()` 函数来交换两个按引用传递的变量的值。现在稍微修改一下程序。

程序 8

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		unsafe {
			int x = 10;
			int y = 20;
			Console.WriteLine("Before swap x = " + x + " y= " + y);
			Obj.swap(&x, &y);
			Console.WriteLine("After swap x = " + x + " y= " + y);
		}
	}
}

class TestClass {
	public unsafe void swap(int* p_x, int* p_y) {
		int temp = *p_x;
		*p_x = *p_y;
		*p_y = temp;
	}
}

这个程序与上一个程序做了同样的事情。但是在这个程序中,我们只编写了一个不安全函数,并从 `Main` 中的不安全块调用该函数。

现在让我们看看另一个展示 C# 中数组用法的程序

程序 9

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		// get value from array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			Console.WriteLine(iArray[iIndex]);
		}
	}
}

这个程序显示了从零到九的数字的平方。

让我们稍微修改一下程序,并将数组作为参数传递给一个函数。

程序 10

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		testFun(iArray);
	}

	public unsafe void testFun(int [] p_iArray) {

		// get value from array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			Console.WriteLine(p_iArray[iIndex]);
		}
	}
}

程序的输出与上一个相同。

现在让我们稍微修改一下程序,并尝试通过指针而不是索引来获取数组的值。

程序 11

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		// get value from array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			Console.WriteLine(*(iArray + iIndex) );
		}
	}
}

在这个程序中,我们尝试通过 `*(iArray + iIndex)` 而不是 `iArray[iIndex]` 来访问数组元素。但是程序会给出以下错误。

Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

um11.cs(21,24): error CS0019: Operator '+' cannot be applied to operands of type 'int[]' and 'int'

在 C# 中,`int*` 和 `in[]` 的处理方式不同。为了更清楚地理解这一点,让我们再看一个程序。

程序 12

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];
		iArray++;

		int* iPointer = (int*)0;
		iPointer++;

	}
}

这个程序中有两种不同类型的变量。首先,变量 `iArray` 被声明为一个数组,第二个变量 `iPointer` 是一个指针变量。现在我将递增两者。我们可以递增指针变量,因为它没有固定在内存中,但我们不能递增 `iArray`,因为数组的起始地址存储在 `iArray` 中,如果我们允许递增它,我们将丢失数组的起始地址。

程序的输出是一个错误。

Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.

um12.cs(13,3): error CS0187: No such operator '++' defined for type 'int[]'

要通过指针访问数组的元素,我们必须固定指针,使其不能被递增。C# 使用 `fixed` 保留字来做到这一点。

程序 13

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		// get value from array
		fixed(int* pInt = iArray)
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			Console.WriteLine(*(pInt + iIndex) );
		}
	}
}

我们可以使用相同的技术将数组传递给接收指针作为参数的函数。

程序 14

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		// get value from array
		fixed(int* pInt = iArray)
		testFun(pInt);
	}

	public unsafe void testFun(int* p_pInt) {

		for (int iIndex = 0; iIndex < 10; iIndex++) {
			Console.WriteLine(*(p_pInt + iIndex) );
		}
	}
}

程序的输出与上一个相同。如果我们尝试访问数组范围之外的元素,它将打印出垃圾数据。

程序 15

using System;

class MyClass {
	public static void Main() {
		TestClass Obj = new TestClass();
		Obj.fun();
	}
}

class TestClass {
	public unsafe void fun() {
		int [] iArray = new int[10];

		// store value in array
		for (int iIndex = 0; iIndex < 10; iIndex++) {
			iArray[iIndex] = iIndex * iIndex;
		}

		// get value from array
		fixed(int* pInt = iArray)
		testFun(pInt);
	}

	public unsafe void testFun(int* p_pInt) {

		for (int iIndex = 0; iIndex < 20; iIndex++) {
			Console.WriteLine(*(p_pInt + iIndex) );
		}
	}
}

这里我们试图从数组中读取 20 个元素,但数组中只有 10 个元素,所以它将在打印完数组元素后打印出垃圾数据。

程序 16

using System;

struct Point {
	public int iX;
	public int iY;
}

class MyClass {
	public unsafe static void Main() {

		// reference of point
		Point refPoint = new Point();
		refPoint.iX = 10;
		refPoint.iY = 20;

		// Pointer of point
		Point* pPoint = &refPoint;

		Console.WriteLine("X = " + pPoint->iX);
		Console.WriteLine("Y = " + pPoint->iY);

		Console.WriteLine("X = " + (*pPoint).iX);
		Console.WriteLine("Y = " + (*pPoint).iY);

	}
}

这里 `pPoint` 是 `Point` 类实例的指针。我们可以使用 `->` 运算符来访问它的元素。

Beta 2 中的更改

当您想使用命令行开关编译程序时,您会在编译器名称后输入程序名称;例如,如果您的程序名为 prog1.cs,那么您将这样编译它

scs prog1.cs

在使用 Beta 1 编写不安全代码时,这工作正常。在 Beta 2 中,Microsoft 为 C# 的命令行编译器添加了一个额外的开关用于编写不安全代码。现在,如果您想编写不安全代码,则必须在命令行编译器中指定 `/unsafe` 命令开关,否则编译器会报错。在 Beta 2 中,如果您想在程序中编写不安全代码,则像这样编译您的程序

csc /unsafe prog1.cs

这里 `prog1.cs` 是程序名称。如果您在不使用 `/unsafe` 开关的情况下编译包含不安全代码的程序,编译器会报错。

© . All rights reserved.