C#中的不安全代码

C#中的不安全代码

[转] http://hi.baidu.com/%DA%E4%C4%B0%B0%EB%D4%B5%BE%FD/blog/item/92587a16bb87bf57f3de32a3.html

C#中的不安全代码
 

版本:1.0
日期:2004-7-28
作者:圈圈(yj_pkucs@hotmail.com)
说明:主要参考msdn

编写不安全代码

MSDN:"尽管实际上对 C 或 C++ 中的每种指针类型构造,C# 都设置了与之对应的引用类型,但仍然会有一些场合需要访问指针类型。例如,当需要与基础操作系统进行交互、访问内存映射设备,或实现一些以时间为关键的算法时,若没有访问指针的手段,就不可能或者至少很难完成。为了满足这样的需求,C# 提供了编写不安全代码的能力。

在不安全代码中,可以声明和操作指针,可以在指针和整型之间执行转换,还可以获取变量的地址,等等。在某种意义上,编写不安全代码很像在 C# 程序中编写 C 代码。"

不安全代码必须用修饰符 unsafe 明确地标记。

这里所谓的编写"不安全代码",就是要跳出.net CLR的限制,自己进行地址分配和垃圾回收.在C++里面,我们定义一个简单的指针,至少要做三件事,1.给他分配内存,2.保证类型转换正确3将内存空间释放,而在.net环境里,CLR将程序员从这些事情里解放出来,用户不再需要直接手工地进行内存操作。但是有时候,比如调用windows底层函数,或是效率上的原因使我们需要自己去操作地址空间,本文主要是说明在c#里面指针的用法.

指针的使用、操作内存

1.& 和 *

    c++里面,我们很熟悉这两个东西.在c#里面他们也一样可以用,只不过含有他们的代码如果不在unsafe 标记下,编译器将会将它拒之门外.当然如果条件编译参数没有/unsafe 也是无法编译通过的(如果用vs.net集成编译环境,则在项目属性页-代码生成节将"允许不安全代码块"设置成true).

    &可以取得变量的地址,但是并不是所有的变量,托管类型,将无法取得其地址.c#里面,普通的值类型都是可以取得地址的,比如struct,int,long等,而class是无法取得其地址的,另外string是比较特殊的类型,虽然是值类型,但它也是受管的.这里插一下另一个运算符,sizeof,它也是仅可用于unsafe模式下.

看下面这段代码,里面简单的用到了*,&,sizeof,还有c#里很少见但c++里大家很熟的->:

    class Class1
    {
        struct Point
        {
            public int x;
            public int y;
        }
        public static unsafe void Main() 
        {
            Point pt = new Point();
            Point* pt1 = &pt;
            int* px = &(pt1->x);
            int* py = &(pt1->y);
            Console.WriteLine("Address of pt is :0x{0:X} ",(uint)&pt);
            Console.WriteLine("size of the struct :{0} ",sizeof(Point));
            Console.WriteLine("Address of pt.x is :0x{0:X} ",(uint)&(pt.x));
            Console.WriteLine("Address of pt.y is :0x{0:X} ",(uint)&(pt.y));
            Console.WriteLine("Address of px is :0x{0:X} ",(uint)&(*px));
            Console.WriteLine("Address of py is :0x{0:X} ",(uint)&(*py));
            Console.ReadLine();
        }
    }

我这里运行的输出结果是:

Address of pt is :0x12F698
size of the struct :8
Address of pt.x is :0x12F698
Address of pt.y is :0x12F69C
Address of px is :0x12F698
Address of py is :0x12F69C

可以看出struct的首地址与第一个成员变量的地址相同,而这个struct的长度是8个字节(=4+4).

2.fixed

虽然在unsafe模式下可以使用指针,但是unsafe的代码仍然是受管代码.CLR会对它的对象进行管理以及垃圾回收,CLR在这个过程中就会对内存进行重定位,可能过一段时间后,根据指针指向的地址就找不到原来的对象了,岂不是说指针在c#里没有什么实际的作用?别急,还有fixed.

fixed 语句设置指向托管变量的指针并在fixed里的语句块执行期间“锁定”该变量(或者是几个变量)。如果没有 fixed 语句,则指向托管变量的指针将作用很小,因为垃圾回收可能不可预知地重定位变量。(实际上,除非在 fixed 语句中,否则 C# 不允许设置指向托管变量的指针。)

看一段与刚才类似的代码,不同的地方是这里的输出地址的语句都在fixed块里.为什么不直接像第一个例子那样直接输出呢?这是因为我们对Point进行了一个小小的改动,它不再是struct了,它现在是class!它是托管类型了,它的成员都没有固定的地址.但是在fixed块里,它的地址是固定的.

        class Point
        {    
            public static int x;
            public int y;
        }
        public static unsafe void Main() 
        {
            Point pt = new Point();
            int[] arr = new int[10];
            //如果不用fixed语句,无论是静态成员还是实例成员,都将无法取得其地址。
            //int* ps = &CPoint.StaticField;
            //PrintAddress(ps);
            fixed (int* p = &Point.x)
                Console.WriteLine("Address is 0x{0:X}",(int)p);
            fixed (int* p = &pt.y) 
                Console.WriteLine("Address is 0x{0:X}",(int)p);
            fixed (int* p1 = &arr[0],p2 = arr)
            {
                Console.WriteLine("Address is 0x{0:X}",(int)p1);
                Console.WriteLine("Address is 0x{0:X}",(int)p2);
            }
            Console.ReadLine();
        }

我这里运行的输出结果是:

Address is 0x3D5404
Address is 0x4BF1968
Address is 0x4BF1978
Address is 0x4BF1978

3.分配内存

在堆栈上分配内存

c#提供stackalloc ,在堆栈上而不是在堆上分配一个内存块,语句为 type * ptr = stackalloc type [ expr ];它的大小足以包含 type 类型的 expr 元素;该块的地址存储在 ptr 指针中。此内存不受垃圾回收的制约,因此不必使用fixed将其固定。此内存块的生存期仅限于定义该内存块的方法的生存期。如果内存空间不足,将会抛出System.StackOverflowException异常.

以下是一段示例程序(form msdn),

public static unsafe void Main() 
{
    int* fib = stackalloc int[100];
    int* p = fib;
    *p++ = *p++ = 1;    //fib[0]=fib[1]=1
    for (int i=2; i<100; ++i, ++p)
        *p = p[-1] + p[-2];//fib[i]=fib[i-1]+fib[i-2];
    for (int i=0; i<10; ++i)
        Console.WriteLine (fib[i]);
    Console.ReadLine();
}
在堆上分配内存

既然有stackalloc,有没有heapalloc呢?答案是没有,c#没有提供这样的语法.想在堆上动态分配内存,只能靠自己想办法了.通过Kernel32.dll里的HeapAlloc()和HeapFree()可以达到这个目的.看下面的代码:

using System;
using System.Runtime.InteropServices;
public unsafe class Memory
{
    const int HEAP_ZERO_MEMORY = 0x00000008;//内存起始地址
    //获得进程堆的句柄
    [DllImport("kernel32")]
    static extern int GetProcessHeap();
    //内存分配
    [DllImport("kernel32")]
    static extern void* HeapAlloc(int hHeap, int flags, int size);
    //内存释放
    [DllImport("kernel32")]
    static extern bool HeapFree(int hHeap, int flags, void* block);
    static int ph = GetProcessHeap();//获得进程堆的句柄
    private Memory() {}
    public static void* Alloc(int size) //内存分配
    {
        void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }
    public static void Free(void* block) //内存释放
    {
        if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
    }
}
class Test
{
    unsafe static void Main() 
    {
        byte* buffer = (byte*)Memory.Alloc(256);
        for (int i = 0; i < 256; i++) 
            buffer[i] = (byte)i;
        for (int i = 0; i < 256; i++) 
            Console.WriteLine(buffer[i]);
        Memory.Free(buffer);
        Console.ReadLine();

}

C#中调用非托管DLL的API详解:http://hi.baidu.com/%DA%E4%C4%B0%B0%EB%D4%B5%BE%FD/blog/item/54b9b63dcce956e83d6d97ad.html

C#非托管代码怎么用(以关机程序为例):http://hi.baidu.com/%DA%E4%C4%B0%B0%EB%D4%B5%BE%FD/blog/item/dbd743c2f76add1d0ef477da.html

将C++托管扩展项目从纯粹的中间语言转换成混合模式:http://www.vckbase.com/document/viewdoc/?id=1403

细致解说C#里使用指针:http://www.va1314.com/bcsl/?viewthread-9508.html

细致解说C#里使用指针

 来源: www.va1314.com/bcsl 作者:wy_qi 跟贴 4 条 进入论坛

C#教程,细致解说C#里使用指针 指针在C\C++里面可是一个好东西,但是到java,.net的时代指针已经被封装起来,对用户不可见,这点java做的非常的完全。.net可能因为还具有一个托管C++,因而指针并没有完全废除,C#还是保留了指针的操做。 & 

指针在C\C++里面可是一个好东西,但是到java,.net的时代指针已经被封装起来,对用户不可见,这点java做的非常的完全。.net可能因为还具有一个托管C++,因而指针并没有完全废除,C#还是保留了指针的操做。 
       要使用指针首先要对使用指针的代码用unsafe进行进行声明,声明和public声明一样,能够对整个类进行声明,也能够是类里面某个方法或者属性。在代码里什么后,还需要修改工程项目的Build属性,让编译器支持指针的操做。
       做好事前的工做就能够使用指针了。指针的使用方法和C++下使用没有太多差别。只需编译器不报错就没有太大问题。
       下面是对指针的一些使用上的理解:
1.  指针类型能够是实体变量(int,double)也能够是enum,同时也支持结构体变量struct。但不能是类。不过空指针能够指向类,只不过空指针不能进行任何操做,也只能把空指针做为传送对象来使用。
2. C#提供一个的关键字stackalloc用于申请堆栈内存。注意,这个申请内存分配的是栈内存,当函数执行完毕后,内存会被自动回收。不过我想用这个栈内存基天性够处理40%的问题,而且使用的时候不必担心内存泄漏问题。
3. .net 好像不间接支持堆内存的申请(这个对.net来说很危险),不过我们能够通过调用win32 api 的方法进行申请。这样就能够处理剩下40%的问题。堆内存申请的方法在MSDN里面有相关的文档,具体实现代码见附1。
来源:www.va1314.com/bc
4.  结构体是一个特殊的对象。他与类的定义就差一个关键字,使用方法也和类一样,能够定义属性,能够定义方法。但是在进行指针操做的时候双方就有很大的差别了。结构体能够通过sizeof()取得大小,大小与结构体里有多少实体变量有关,但是如果struck里定义了类的对象,或者指针,sizeof可能会编译不过(void* 的空指针例外,不过需要在结构体声明处加上unsafe)。
5. fixed关键字:目前了解的不多,不过有一个很实用的例子能够让指针能够和.net里的数组进行交互操做: 

                byte[] buffer = new byte[100];
                fixed (byte* p = buffer)
                {
                    P[0] = 123;
                    ……
                }

6.  其它
7.  




附1:
    public unsafe class Memory
    {
        // Handle for the process heap. This handle is used in all calls to the
        // HeapXXX APIs in the methods below.
        static int ph = GetProcessHeap();
        // Private instance constructor to prevent instantiation.
        private Memory() { }
        // Allocates a memory block of the given size. The allocated memory is
        // automatically initialized to zero.
        public static void* Alloc(int size)
        {
            void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
            if (result == null) throw new OutOfMemoryException();
            return result;
        }
        // Copies count bytes from src to dst. The source and destination
        // blocks are permitted to overlap.
        public static void Copy(void* src, void* dst, int count)
        {
            byte* ps = (byte*)src;
            byte* pd = (byte*)dst;
            if (ps > pd)
            {
                for (; count != 0; count--) *pd++ = *ps++;
            }
            else if (ps < pd)
            {
                for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
            }
        }
        // Frees a memory block.
        public static void Free(void* block)
        {
            if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
        }
        // Re-allocates a memory block. If the reallocation request is for a
        // larger size, the additional region of memory is automatically
        // initialized to zero.
        public static void* ReAlloc(void* block, int size)
        {
            void* result = HeapReAlloc(ph, HEAP_ZERO_MEMORY, block, size);
            if (result == null) throw new OutOfMemoryException();
            return result;
        }
        // Returns the size of a memory block.
        public static int SizeOf(void* block)
        {
            int result = HeapSize(ph, 0, block);
            if (result == -1) throw new InvalidOperationException();
            return result;
        }
        // Heap API flags
        const int HEAP_ZERO_MEMORY = 0x00000008;
        // Heap API functions
        [DllImport("kernel32")]
        static extern int GetProcessHeap();
        [DllImport("kernel32")]
        static extern void* HeapAlloc(int hHeap, int flags, int size);
        [DllImport("kernel32")]
        static extern bool HeapFree(int hHeap, int flags, void* block);
        [DllImport("kernel32")]
        static extern void* HeapReAlloc(int hHeap, int flags,
           void* block, int size);
        [DllImport("kernel32")]
        static extern int HeapSize(int hHeap, int flags, void* block);

    } 

 

 

 

posted @ 2010-01-18 22:33  星释天狼  阅读(390)  评论(0)    收藏  举报