C#学习之unsafe

摘要:
但是,通过使用unsafe关键字,可以定义可以使用指针的不安全上下文。不安全和非托管的区别:managedcode是在CLR的监督下运行的程序。不安全是管理和非管理之间的桥梁。它使托管代码能够使用指针来控制和操作内存。不安全代码介于两者之间。它也在CLR环境中执行,但我们可以直接操纵内存。还应注意两点:1)指针必须放在不安全区域;unsafe关键字也可以用作类或方法的修饰符。2) 在fixed中,p的定义不能写在其他地方,fixed关键字只能用于不安全区域。

为了保持类型安 全,默认情况下,C# 不支持指针算法。不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。

unsafeC# 程 序中的使用场合:

1)实时应用,采用指针来提高性能;

2)引用非.net DLL提供的如C++编写的外部函数,需要指针来传递该函数;

3)调试,用以检测程序在运行过程中的内存使用状况。

使用unsafe 的利弊:

好处:性能和灵活性提高;可以调用其他dll的函数,提高了兼容性;可以得到内存地址;

坏处:非法修改了某些变量;内存泄漏。

unsafe 与unmanaged的区别:

managed code是在CLR监管下运行的程序。以下任务由CLR来执行:管理对象内存,类型安全检测和冗余处理。

unmanaged code也就是能由程序员直接进行内存操作的程序。

unsafe 是介于managed和unmanaged之间的桥梁,它使得managed code也能使用指针来控制和操作内存。

unsafe 的使用:


unsafe可以用来修饰类、类的成员函数、类的全局变量,但不能用来修饰类成员函数内的局部变量。 编译带有unsafe 代码的程序也要在 “configuration properties>build” 中把允许unsafe 代码设为真。

但是在managed code中使用unsafe 时也要注意,正因为CLR可以操作内存对象,假如你写了一下代码:

      public unsafe void add(int *p)
      {
          *p=*p+4;
      }

p的地址值可能会在运行过程中被CLR所修改,这通常可采用fixed来处理,使指针所指向的地址不能被改变。如下:

      fixed(int *p=& value)
        {
            add(p);
        }

托管代码 (managed code):由公共语言运行库环境(而不是直接由操作系统)执行的代码。托管代码应用程序可以获得公共语言运行库服务,例如自动
垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。

非托管代码(Unmanaged Code):在公共语言运行库环境的外部,由操作系统直接执行的代码。非托管代码必须提供自己的垃圾回收、类型检查、安全支
持等服务;它与托管代码不同,后者从公共语言运行库中获得这些服务。
Unsafe的代码介于这两者之间,它也是在CLR的环境中执行,但是我们可以直接操作内存。只要我们的代码包含下面三个指针操作符之一就需要使用Unsafe
关键字:
  • *
  • &
  • ->

    例如:

    unsafe static void ChangeValue(int* pInt)
    {
    *pInt = 23;
    }

    上面的代码由于是在CLR下托管执行,为 了减少内存碎片C#的自动垃圾回收机制会允许已经分配的内存在运行时进行位置调整,所以如果我们多次调用的话就可能
    导致指针指向其他的变量。比如*pInt为 指向一个变量的地址为1001,CLR在重新内存整理分配后该变量 就存储在地址为5001的地方。而原来1001的地方可能会
    被分配其他变量,要解决这个问题我们就需要使用Fixed关键字。

    fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。Fixed 还可用于创建固定大小的缓冲区。如下面例子:

    using System;
    class CaryData
    {
    public int data;
    }

    class CProgram
    {

    unsafe static void ChangeValue(int* pInt)
    {
    *pInt = 23; //3为这个指针的地址赋值23
    }

    public unsafe static void Main()
    {
    CaryData cd = new CaryData();
    Console.WriteLine("改变前: {0}", cd.data);

    fixed (int* p = &cd.data) // 1把整形的地址赋给了指针P
    {
    ChangeValue(p); //2专递指针
    }
    Console.WriteLine("改变后: {0}", cd.data); //4由于cd.data的和*p地址相同,所以cd.data 的输出是23
    }
    }

    注意要勾选项目属性中生成标签的允许不安全代码。

  • T_Account 
    
    ret;
    unsafe
    {
    fixed (void * ptr = body)
    {
    ret = *((T_Account *)ptr); // 转换指针为(T_Account*),再获得指针的值也就是T_Account类型值
    }
    }
    T_Account 
    
    x = new 
    
    T_Account
    
    ();
    x.ID = 12;
    x.Name = "thisistest" ;
    x.Native_Currency = "USD" ;
    byte [] message = new byte [T_Account .Size];
    unsafe
    {
    void * ptr = &x; //将地址赋给这个指针
    fixed (void * des = message) // 值赋给了这个地址
    {
    MemoryUtility .CopyData(des, ptr, T_Account .Size);
    }
    }



    (*) unsafe 和 fixed

    unsafe
    {              
        int[] array = new int[10];
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = i;
        }
        fixed (int* p = array)
        {
            for (int i = 0; i < array.Length; i++)
            {
                System.Console.WriteLine(p[i]);
            }                   
        }              
    }

    指针在c#中是不提倡使用的,有关指针的操作被认为是不安全的(unsafe)。因此运行这段代码之前,先要改一个地方,否则编译不过无法运行。
    修 改方法:在右侧的solution Explorer中找到你的项目,在项目图标(绿色)上点右键,选最后一项properties,然后在Build标签页里把Allow unsafe code勾选上。之后这段代码就可以运行了,你会看到,上面这段代码可以像C语言那样用指针操纵数组。但前提是必须有fixed (int* p = array),它的意思是让p固定指向数组array,不允许改动。因 为C#的自动垃圾回收机制会允许已经分配的内存在运行时进行位置调整,如果那样,p可能一开始指的是array,但后来array的位置被调整到别的位置 后,p指向的就不是array了。所以要加一个fixed关键字,把它定在那里一动不动,之后的操作才有保障。

    另有两点需要注意:

    1)指针的使用必须放在unsafe的区域里;unsafe关键字也可作为类或方法的修饰符。

    2)fixed (int* p = array)中,p的定义不能写在别处,而且fixed关键字也只能在unsafe区域里使用。

    (*) 略简洁的unsafe写法

        class Program
        {
            unsafe public static UInt16 Htons(UInt16 src)
            {
                UInt16 dest;
                // 不能照搬C的源代码,因为有些类型长度不一样,如char(2字节),long(8字节)
                // ((char*)&dest)[0] = ((char*)&src)[1];
                // ((char*)&dest)[1] = ((char*)&src)[0];
                ((byte*)&dest)[0] = ((byte*)&src)[1];
                ((byte*)&dest)[1] = ((byte*)&src)[0];
                return dest;
            }

            public static UInt16 ConciseHtons(UInt16 src)
            {
                UInt16 dest;
                unsafe
                {
                    ((byte*)&dest)[0] = ((byte*)&src)[1];
                    ((byte*)&dest)[1] = ((byte*)&src)[0];
                }           
                return dest;
            }
           
            static void Main()
            {
                UInt16 val = 1;

                // 如果方法是unsafe的,则必须在unsafe block里调用
                unsafe
                {               
                    val = Htons(val);
                }
                Console.WriteLine(val);

                // 更简洁的写法是把unsafe block写在函数内部
                val = ConciseHtons(val);
                Console.WriteLine(val);
            }               
        }

    (*) stackalloc

    stackalloc的用处仅仅是把数组分配在栈上(默认是分配在托管堆上的)。

        class MyClass
        {
            public int val;
        }

        class Program
        {               
            static void Main()
            {           
                unsafe
                {               
                    MyClass *p = stackalloc MyClass[1]; // Error!! 如果类型要放在托管堆上则不行,如果MyClass是struct就OK了
                    p->val = 1;

                    int *iArray = stackalloc int[100];  // OK,在栈上创建数组, int类型本身就是放在栈上的
                }           
            }               
        }

    注意:指针指向的内存一定要固定。凡是C#里的引用类型(一切类型的 数组都是引用类型 )都是分配在托管堆上的,都不固定。有两种方法强制固定,一种是用stackalloc分配在栈上,另一种是用fixed 分配在堆上。

    免责声明:文章转载自《C#学习之unsafe》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

    上篇新建的虚拟机如何改IP地址CSS禁止选择文本功能(兼容IE,火狐等浏览器)下篇

    宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

    相关文章

    delphi之多线程编程(一)

    本文的内容取自网络,并重新加以整理,在此留存仅仅是方便自己学习和查阅。所有代码均亲自测试 delphi7下测试有效。图片均为自己制作。 多线程应该是编程工作者的基础技能, 但这个基础我从来没学过,所以仅仅是看上去会一些,明白了2+2的时候,其实我还不知道1+1。 开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解. 先试试这个:...

    1-嵌入式面试题库

    嵌入式工程师:主要从事嵌入式软件开发工作,涉及应用层以及底层软件开发和设计的工作。以应用为中心,计算机技术为基础,软硬件可裁剪,适用于应用系统对功能、可靠性、成本、体积、功耗有严格要求的专用计算机系统。嵌入式产品一般由嵌入式微处理器、外围硬件设备、嵌入式操作系统以及用户程序等四个部分构成,用于对其他设备控制、监护、管理。 面试题目(自我介绍/项目/代码量/...

    malloc原理和内存碎片

    当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:1、检查要访问的虚拟地址是否合法2、查找/分配一个物理页3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)4、建立映射关系(虚拟地址到物理地址)重新执行发生缺页中断的那条指令如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。 内存分配的原理 从操作系统角度...

    MySQL、MongoDB、Redis 数据库之间的区别

    NoSQL 的全称是 Not Only SQL,也可以理解非关系型的数据库,是一种新型的革命式的数据库设计方式,不过它不是为了取代传统的关系型数据库而被设计的,它们分别代表了不同的数据库设计思路。 MongoDB:它是一个内存数据库,数据都是放在内存里面的。对数据的操作大部分都在内存中,但MongoDB并不是单纯的内存数据库。MongoDB 是由 C++...

    关于redis性能问题分析和优化

    一、如何查看Redis性能 info命令输出的数据可以分为10个分类,分别是: server,clients,memory,persistence,stats,replication,cpu,commandstats,cluster,keyspace 为了快速定位并解决性能问题,这里选择5个关键性的数据指标,它包含了大多数人在使用Redis上会经常碰到的性...

    [C++]智能指针的实现与使用

    智能指针 智能指针是当我们在使用对象时,有时会把对象的内存分配在堆上忘记释放,导致内存泄露,并且当多个指针共享同一个对象的内存时,容易出现重复释放内存,导致错误。 我们针对所需要共享的对象,手动完成一个智能指针类来代替该类别的指针,自动帮我们释放内存,共享内存。以一个共享Object类的对象数据来说明两个版本的共享指针的实现。 class Object...