体验Visual C++ 2005的现代语言特性_VC++语言_黑客防线网安服务器维护基地--Powered by WWW.RONGSEN.COM.CN

体验Visual C++ 2005的现代语言特性

作者:黑客防线网安网站维护基地 来源:黑客防线网安网站维护基地 浏览次数:0

本篇关键词:Visual
黑客防线网安网讯:对于C++语言的爱好者来说,Visual Studio .NET 2003中C++编译器的引入绝对令人垂涎欲滴。Visual C++ .NET 2003中有98%的部分与ISO C++标准保持一致,这使它比以往任何版本更为靠近这些标准,而且它还加入了对一 ...

对于C++语言的爱好者来说Visual Studio .NET 2003中C++编译器的引入绝对令人垂涎欲滴Visual C++ .NET 2003中有98%的部分与ISO C++标准保持一致这使它比以往任何版本更为靠近这些标准,而且它还加入了对一些功能(如局部模板专用化)的语言支持它还包括增强的缓冲区安全检查和改进的编译器诊断功能。C++开发人员就像C#和Visual Basic .NET开发人员一样,可以使用拖放窗体设计器来构建健壮的Windows窗体应用程序。该编译器还包含了针对Intel Pentium 4和AMD Athlon处理器的优化

如果您对Visual C++ .NET 2003感到兴奋不已,您将会更加疯狂地爱上它的下一个版本Visual C++ 2005。Visual C++ 2005为.NET开发提供了既优雅又强大的新语法支持。全新的优化技术已经使Microsoft产品的运行速度提高了30%。它通过新的编译模式来确保Microsoft .NET Framework通用语言基础结构(Common Language Infrastructure,CLI)的一致性和可验证性,并且具有新的互操作(interop)模型,这不仅提供了本机和托管环境的无缝融合,而且还在跨边界的情况下提供了完全控制。该编译器增强了前两个版本中提供的缓冲区安全检查选项,并且还包括了C++应用程序普遍使用的以安全性为中心的库的新版本。它提供了对OpenMP标准以及64位平台(其中包括Intel Itanium和AMD64芯片)的支持。它解决了混合DLL加载问题,并且提供了对Double P/Invoke性能问题的运行时的自动清除。还可以列出许多增强和改进。正如C++小组的一位架构师告诉我的,“兄弟,C++总算找到了属于自己的位置!”

C++/CLI新的语法
在我们这些人中,有多少人讨厌使用前两个版本C++的托管扩展语法并且认为其中尽是错误?有多少人认为Visual C++没有被当作基于.NET的头号语言?很明显,当中的大多数人都是这样的(其中包括开发团队本身,只要阅读一下他们的Blog就知道了)。Visual C++小组的人听到了这些抱怨,于是开始开发Visual C++。与Visual Studio .NET 2002一起引入的C++语法的托管扩展就像恐龙一样消失殆尽了,因为引入了修订的语言定义,从而产生了一种有吸引力的新语法。

设计小组对于这个版本在语言设计方面有几个重要的目标。首先(可能对那些认为代码是一种艺术的人来说最为重要),他们想要确保编程人员在编写C++代码时感到很自然,而且通过对ISO C++标准的纯粹扩展可以提供一种优雅的语法。他们想要让编程人员轻松地使用C++编写可验证的代码来支持部分信任的情况,例如SQL Server 2005中的ClickOnce部署、窗体设计支持和托管代码宿主。他们不想为任何比C++“更低级”的语言提供任何空间。他们想把.NET的全部强大功能带给C++,而与此同时也把C++的强大功能带给.NET。他们在各个方面都取得了骄人的成功。

新的扩展规范叫做C++/CLI,并且现在正在进行标准化的工作。要体验一下新的语言扩展,请参见于2003年9月21日在线公布的候选基本文档。

对任何阅读采用新语法编写的代码的人来说,最容易注意到曾经在托管扩展中以双下划线关键字定义垃圾回收类、属性等的流行做法已经成为过去。虽然这样一些关键字仍然保留着,并且还加入了一些新的关键字,但它们现在已经不经常使用了,并且也不会影响到代码的可读性。这些双下划线的关键字由两种新类型的关键字来代替:上下文敏感的关键字和间隔排列的关键字。上下文敏感的关键字是只有在特定上下文中才使用的关键字,而间隔排列的关键字是在与其他关键字组合时才使用的关键字。例如,托管扩展中的__property关键字会被property关键字取代(不仅如此,用来定义一个属性及其访问器的全部语法都有了显著的改进,使得声明看起来非常类似于用C#编写的代码。请参见图1中的示例)。这并不影响在编码时将“property”用作变量的名称。在声明某一类型的属性这一上下文中,被解析为“property”的标记仅被视为一个关键字。

图 1 语法对比

托管扩展语法
public __gc __sealed class Student
{
    private:
        double m_grade;
        String* m_name;
    public:
        __property double get_Grade() { return m_grade; }
        __property void set_Grade(double newGrade) { m_grade = newGrade; }

        __property String* get_Name() { return m_name; }
        __property void set_Name(String* newName) { m_name = newName; }
}
C++/CLI 语法
public ref class Student sealed
{
    private:
        double m_grade;
    public:
        // standard property syntax
        property double Grade
        {
            double get() { return m_grade; }
            void set(double newGrade) { m_grade = newGrade; }
        }

        // trivial property
        // compiler can generate accessors and backing store
        property String^ Name;
}

在新的语法中,类型以“形容词类”的形式声明,其中,形容词描述您正在创建的类是什么类型,如下所示:
class            N  { /*…*/ };  // native type
ref class        R  { /*…*/ };  // CLR reference type
value class      V  { /*…*/ };  // CLR value type
interface class  I  { /*…*/ };  // CLR interface type
enum class       E  { /*…*/ };  // CLR enumeration type

在之前的语言版本中,类型被声明时就可以确定它的使用范围及方式。只有本机类或结构和托管值类型可以在堆栈上创建。托管引用类总是存在于托管堆当中。在Visual C++ 2005中,所有的类型,无论是本机的还是托管的,都在堆栈上创建,它使用基于堆栈的确定性清理语义来完成这一功能。

要在本机堆上实例化类型T的一个对象,可以使用“new T”。这样就可以返回一个指向本机堆上的对象地址的指针(一个在Visual Studio .NET 2002和Visual Studio .NET 2003中称为__nogc指针概念)。为了在托管堆上实例化类型T的一个对象,Visual C++ 2005引入了gcnew这一关键字,它与new关键字的使用方式相同。调用“gcnew T”可以返回指向托管堆中整个对象的一个句柄。句柄(handler)是在Visual C++ 2005中引入的一个新构造,它类似于托管扩展中的__gc指针。要在堆栈上实例化T类型的对象,标准的“T t;”声明就已经足够了。

为了公平起见,还是介绍一下是如何定义实例化的。托管引用类总是存在于托管堆当中,而本机类型总是存在于堆栈或本机堆当中。当一个托管引用被声明为存在于堆栈上时,编译器实际上还会在托管堆上对其进行实例化。

这样会带来一些问题。当在堆栈上的实例超出它的使用范围时会怎样?这个实例将如何被清理掉?许多C#开发人员一直在抱怨C#语言缺少确定性清理。C#语言提供using关键字来简化IDisposable对象的处置,但这需要额外的代码,而且与C++开发人员所熟悉的析构函数的模式相比显得尤为笨拙。在C#中,安全的清理工作在默认情况下是无法进行的,它需要进行显式的编码。例如,请考虑图3中的第一个C#代码片断。StreamReader对象是在托管堆上声明的。当这个方法执行完毕之后,StreamReader的实例就没有任何引用存在了。然而,直到垃圾回收器运行时,这个对象才会被清理掉。直到那时,所用的文件才会被关闭,而在此之前,应用程序会一直占用其打开的文件句柄。要添加确定性清除,必须使用由利用非托管资源的类实现的IDisposable接口。

图3中的第二个代码示例显示了C#中的新代码的外观。其实这种方法也未尝不可,而且也还算有一定的可读性。但当开始加入更多需要清理的对象时,您的代码就会变得越来越难懂。而且,任何您忘记清理的对象都会在最后垃圾回收器实际运行时为finalizer线程增加负担。而与此同时,也许已经锁定了一些有价值的资源。这一点在查看Visual Basic .NET中的同等实现时显得尤为不堪,同样如图3所示(尽管Visual Basic 2005增加了与C#相类似的using语句)。

图 3 确定性清理
实现 代码
没有确定性清理的 C# string ReadFirstLineFromFile(string path)
{
  StreamReader reader = new StreamReader(path);
  return reader.ReadLine();
})
具有确定性清理的 C# string ReadFirstLineFromFile(string path)
{
  using (StreamReader reader = new StreamReader(path))
  {
    return reader.ReadLine();
  }
}
具有确定性清理的 Visual Basic .NET Function ReadFirstLineFromFile( _
    ByVal path As String) As String
  Dim reader As StreamReader
  Try
    reader = New StreamReader(path)
    ReadFirstLineFromFile = reader.ReadLine()
  Finally
    If Not reader Is Nothing Then _
      CType(reader, IDisposable).Dispose()
  End Try
End Function
具有确定性清理的 C++ String^ ReadFirstLineFromFile(String^ path)
{
  StreamReader reader(path);
  return reader.ReadLine();
}

现在,Visual C++ 2005在任何类型上都提供了可以具有析构函数/finalizer的功能,无论这种类型是托管的还是本机的。在它为托管类型的情况下,编译器会将析构函数映射到IDisposable::Dispose方法。这意味着能够用C++语言编写同样的方法,如图3中的第四个代码片断所示,其中,阅读器的析构函数/Dispose方法将会自动被调用,就像在C#中使用“using”语句一样。当在堆栈上创建某一类型时,它的析构函数会在它超出其使用范围时被调用。

托管扩展的一个最大的问题是对指针的使用。指针被用于各种各样的任务,而其情况也是复杂多变的,因而非常难以理解。在某一特定的代码段中要解读自己在和哪一种指针打交道需要有一定程度的天赋。这种复杂性在下一个版本中会被去掉。在Visual C++ 2005中,指针还是原原本本的C++指针。它们指向稳定的对象,而您则可以用指针进行算术操作。指向对象的指针的生命周期必须由开发人员显式管理。当使用指针时,运行库不会负责对指针带来的垃圾进行清理。

现在让我们看一下Visual C++ 2005的设计人员是如何解决这一问题的。与Visual Studio .NET 2003和Visual Studio 2005中使用new运算符返回指针不同,gcnew运算符返回一个“句柄”这是一种新构造,在语法中用“^”符号来表示。该句柄引用托管堆中的整个对象。也就是说,它们不能用来指向类型的内部,而编译器对它们的使用有许多限制,以此来强制执行这种行为,而这也可以帮助开发人员正确并安全地使用句柄。句柄不允许进行指针算术运算,也不可以被强制转换为空指针或是任何整数类型。然而,星号和箭头运算符仍被用来取消对它的引用。

这并不意味着您不能再获得一个指向垃圾回收堆上的指针。与在C#中组合&运算符与固定的关键字相似,在Visual C++ 2005中,pin_ptr抽象类型允许您检索指向托管堆上对象的钉住指针。只要这个指针存在,托管堆中的对象就会被钉住,这可以防止垃圾回收器在回收的过程中移动它。Visual C++ 2005还引入了跟踪引用运算符,用百分号(%)来表示。当在C++中引入本机的&引用运算符时,大多数开发人员都知道可以把它理解成一个指向对象的指针,在使用时是由编译器来自动清除的。在大多数情况下,%对^而言就像&对*一样。

在托管的环境下,将本机引用指向托管对象就像将本机指针指向托管对象一样危险。在指针与引用幕后的基本原理就是:被引用的对象并不会被四处移动。跟踪引用和本机引用很相似,唯一例外的是,跟踪引用引用托管堆上的对象,并且对其进行“跟踪”,即便是它们被垃圾回收器移走。百分号运算符也被用来“提取托管对象的地址”,所以就像&运算符在应用于本机类型时返回指向该对象的指针一样,%运算符在应用于托管引用类型时会返回一个指向该对象的句柄。

一般来说,当C++开发人员知道标准在控制它们的语言时,他们会感到心安理得。由于这个原因,为了促进第三方的采用,并确保语言向前发展的稳定性,这种新的语法采集众长而成为一个称为C++/CLI的提议标准。.在2003年10月,ECMA选举出了一个特别工作组,名为TG5,致力于分析和采用这一标准,就像WG21作为ISO C++的管理团体一样。.实际上,WG21中的关键人物也在TG5中工作。.他们的计划是在2004年年底将其C++/CLI标准化。

互操作选项
在Visual Studio .NET 2003的所有基于.NET框架的语言中,Visual C++ 7.1提供了最好的互操作功能。它具有实现实际的互操作方案所必需的功能,Quake II到.NET框架的移植便是例证,具体细节请访问http://www.vertigosoftware.com/Quake2.htm。Visual C++ 2005进一步扩展了这一功能。

在托管与本机环境中,使用.NET 互操作有四种主要途径:COM 互操作可以使用Runtime Callable Wrappers(RCW)与COM Callable Wrappers(CCW)来实现。.通用语言运行库(CLR)负责类型封送(除非在极少的情况下使用自定义封送拆收器),并且这些调用的开销很大。需要非常小心地尽量避免接口往来过于频繁,否则就会出现很严重的性能问题。还需要保证这些包装一直与其底层的组件保持一致。也就是说,在简单的互操作场景而试图使用大量的本机COM代码时,COM 互操作非常有用。

第二种互操作选择是使用P/Invoke。要达到此目的,可以使用DLLImport属性,并且在方法声明中为想要导入的函数指定属性。封送是按照它在声明中的指定方式来处理的。然而,只有在有代码需要通过DLL导出公开必须的功能时,DLLImport才是有用的。

当需要从本机代码调用托管代码时,CLR宿主也是一种选择。在这种情况下,本机应用程序必须驱动所有的执行:设置主机、绑定到运行库、启动主机、检索适当的应用程序域、设置调用上下文、查找所需的程序集和类,并调用所需类上的操作。在控制发生什么以及何时发生方面,这无疑是最健壮的解决方案之一,但这也会带来让人难以置信的枯燥,并需要许多自定义代码。

第四种选择,也有可能是最简单并最可行的选择,就是使用C++的互操作功能。通过设置/clr开关,编译器会生成中间代码(MSIL)而不是本机代码。唯一被生成为本机代码的是那些无法被编译成中间代码的代码,其中包括带有内联asm块的函数,以及使用像Streaming SIMD Extensions (SSE)这样一些特定于CPU的固有特性的操作。Quake II就是使用/clr开关移植到.NET的。Vertigo软件小组花费了一天的时间将原来由C编写的游戏代码成功地编译成C++代码,然后设置了/clr开关。他们的代码很快就可以运行在.NET框架上。在不添加任何附加的二进制文件而只是简单地加入适当的头文件的情况下,托管C++和本地C++可以相互调用,而无需部分开发人员做一些额外的工作。编译器负责创建适当的)转换代码来往返在两种环境之间。

这给C++开发人员带来了一些问题。问题之一就是现在声名狼籍的混合DLL加载问题,Visual Studio .NET 2002和Visual Studio .NET 2003的用户都受此问题的影响。如果正在运行加载器锁(Loader Lock)内的本机代码并且引用一个还没有加载的程序集中的托管类型,CLR会非常友善地加载这一程序集。它是通过调用LoadLibrary来实现的。当然,LoadLibrary会尝试获得加载器锁,这会碰到死锁问题。开发人员和产品经理如果听说这个问题在即将推出的版本中会得到解决一定非常高兴。

/clr开关对C++开发人员来说是一个极好的工具,但它也有一些缺点。正如本文之前提到的一样,由/clr开关产生的映像既包含本机代码又包含托管代码,这有时会导致问题的出现。首先,这些混合映像并不是遵循CLI的(这意味着,例如,它们将无法在Rotor上运行)。它们有本机的入口点,而当频繁跨越托管边界时会带来极大的转换开销。但最重要的是,这些本机入口点的存在会对使用包括反射在内的程序集的工具带来极大的危害。为了使用反射来检查一个映像,必须首先加载程序集并执行它。只有在所有的初始化都执行完毕时,反射才能检查元数据。遗憾的是,反射无法正确地加载包含有本机入口点的托管程序集。

此外,Visual Studio .NET 2003很少生成可验证的代码,即使它这样做,它花费在处理一些其他重要问题上的时间也会比较多。而中间代码对无法验证的指令有着一流的支持(可以进行指针算术运算,执行间接加载和访问本机堆),可验证的代码能够处理一些需要部分信任的情况,而这又可以支持Visual Studio 2005提供一些丰富功能。ClickOnce部署依赖于部分信任,与SQL Server 2005中的托管代码宿主一样。Visual C++ 2005开发小组的主要目标之一就是让编译器能够在开发人员开发非混合和可验证的映像产品时有所帮助。它们通过引入两个新的编译器开关来实现这一点:/clr:pure和/clr:safe。不过,在深入讲解如何使用这些新开关之前,需要分析一下C++ 互操作的工作原理。

正常运行(It Just Works)
在Visual Studio .NET 2003中,C++ 互操作技术被称为IJW或“正常运行(It Just Works)”。在即将推出的版本中,这被改为一个更具描述性的名称“互操作技术”。那么,它是如何“正常运行”的呢?对于每个由应用程序使用的本机方法而言,编译器同时创建了一个托管的入口点和一个非托管的入口点。它们中的一个是实际的方法实现,而另外一个是转发转化代码,它创建适当的转换并进行任何必要的封送处理。托管入口点几乎总是实际的方法实现,唯一的例外是该方法的代码无法用中间代码表示或者开发人员使用“#pragma unmanaged”编译器指令来强制要求将入口点实现为本机代码。

当使用一个IJW转发转化代码时(例如,当本机入口点是转发转化代码时),编译器提供转化代码的实现,并通过一个偏移量或导入地址表(Import Address Table,IAT)跳转来调入实际的实现。IJW转化代码处理的合理时间大约在50到300个周期之间,不过,精心设计的测试用例可以使这个数字减至10那么小。当转发的转化代码是中间代码时,托管的P/Invoke就会派上用场。P/Invoke仅包含一个声明而没有实际的方法实现;CLR提供了对转化代码的运行时支持的功能。这些转发的转化代码通常都会比同等配置的本地机器实现稍微慢一点点。

如上所述,使用IJW使每个函数都有两个入口点,一个托管的接口和一个非托管的接口。但某些构造需要这些入口点的调用地点在编译时进行填充(例如函数指针和vtable)。而如果编译器在编译时无法知道运行时调用地点的托管状态,则它应该选择哪一个入口点呢?在Visual Studio .NET 2003中,编译器总是会选择非托管入口点。当然,如果调用方确实是托管的,则上述做法就会造成一些麻烦,这称为Double P/Invoke问题。在这种情形下,托管调用对非托管转化代码进行的转换刚好又转换回托管代码,这样的操作会导致几个大的不必要的开销。

Visual C++ 2005提出了几个解决方案。第一个方案就是使用__clrcall关键字,通过这个关键字,可以指定是否基于每个方法发出非托管入口点。使用这个关键字添加函数声明可以防止生成非托管入口点(这样做的一个缺点就是该函数就不能被本机代码直接调用)。__clrcall关键字也可以放置在函数指针上,这样在编译器有所选择的情况下,可以使用托管入口点来填充该指针。Visual C++ 2005提供的第二个解决方案是通过运行库检查来自动消除Double P /Invoke问题,而cookie将帮助运行库确定是否可以跳过非托管的转化程序,从而将调用直接转发至托管入口点。不过,这一功能不可能最终解决问题。

第三个解决方案是纯中间代码。新的/clr:pure编译器选项指示编译器生成一个不包含本机构造的纯托管映像。这样不仅可以产生遵循CLI的程序集来支持部分信任的情况,而且通过防止生成非托管的转化代码解决了Double P/Invoke问题。结果是,每个函数只有一个入口点(托管入口点),这样,虚表(vtable)和函数指针就决不会使用非托管入口点进行填充了。

然而,仅仅因为代码是遵循CLI的并不意味着它就是可验证的,而这对于支持低信任级别的情况(例如当从文件共享加载代码时)是一个重要的目标。所以,Microsoft引入了一个更为严格的编译器选项,称为/clr:safe。对于C++开发人员来说,这是可验证性的圣杯。使用这个开关会使编译器确保生成的程序集是完全可验证的;任何无法验证的结构都会产生编译时错误。例如,试图将一个整型指针编译成一个变量将会产生这样的错误:“int* = this type is not verifiable”,并指出包含非法结构的行。在一些情况下,走向这个极端是适当的。例如,将要作为SQL Server 2005中的存储过程运行的所有托管C++代码都应该使用此选项进行编译。

数据及代码的托管与非托管环境中,不包含任何/clr选项将导致生成完全的本机映像。使用/clr选项将会产生可以包含托管与非托管代码和数据的混合映像。通过使用/clr:pure选项生成的纯中间代码不会包含任何非托管代码,尽管这仍不能保证是可验证的,而且可以包含本机类型。安全中间代码是可验证性的最终目标(只针对.NET框架而言)。简而言之,这两种新的编译模式都将使以前不可能实现或者难以实现的多种情况变为现实。

优化
所有优秀的软件开发人员都想要确保他们的软件能够正常运行。编译器编写人员是一种特殊的开发人员;它们的代码不仅需要正常运行,而且他们的代码生成的代码必须尽可能地具有高的效率。由于这个原因,任何成功的编译器的背后都要有一个好的优化支持。在这方面,Visual C++ 2005是无可挑剔的。

由于Visual Studio .NET 2002和Visual Studio .NET 2003在本机代码的性能提高方面做了许多的工作,所以它们加入了对C++编译器的一些惊人的优化。在加入了SSE和SSE2体系结构的同时,它们还提供了针对Intel Pentium IV的支持。最为显著的是加入了全局程序优化(Whole Program Optimization,WPO),它允许链接器在将每个经过编译的.cpp文件变成.obj文件时对整个程序进行优化。这些对象文件和普通的对象文件有所不同,因为它们包含的不是本机代码,而是用来在编译器前端和后端进行通信的中间语言。然后,链接器就能将所有这些文件优化成一个大的单元,从而提供更多的内联机会、更好的堆栈对齐方式和在各种情况下使用自定义调用约定的可能性。Visual C++ 2005使用称为自顶向下、自底向上分析这样的新功能来改进(WPO)。但是大的改进是以特性引导最优化(Profile Guided Optimization,POGO)的形式出现的,在编译器中提供的这种全新的功能将会对性能有所改进。

就编译器而言,对源代码的静态分析将会留下许多悬而未决的问题。如果在一个if语句中比较两个变量,第一个变量比第二个变量大的几率是多少?在一个switch语句中,哪一个case被选中的次数最多?哪些函数最常使用,而哪些代码常常被忽略?如果编译器在编译时知道代码在运行时应该如何使用的话,它就可以为大多数情况进行优化。这正是Visual C++ 2005编译器所能够做到的。

POGO第一步需要编译代码并将其链接成一个规范构建,它具备一组分析探测器。当使用WPO时,由编译器生成并导入到链接器的对象文件是由中间语言而不是本机代码构成的。这些探测器分为两种:值探测器和计数探测器。值探测器用来构造变量存放的值的直方图,而计数探测器用来跟踪您通过该应用程序往返某一特定路径的次数。当应用程序运行并正常使用时,数据是从所有这些探测器中集合汇集而成的,并被写入一个配置文件数据库。这些配置文件数据和原始的.obj文件一起被导入到链接器。链接器能够分析配置文件数据,确定应该应用的其他优化,并生成一个新的非规范应用程序构建。这只是一个经过编译的版本,而不是一个可以用来发布给客户的规范版本。

特性引导最优化(POGO)支持各种各样的优化。以计数探测器为基础,可以在每个函数调用地点做出内联决策。通过使用值探测器,可以重新排列switch和if-else构造,这样就可以提取出最常用的值,并且避免在找到常见的case之前进行额外的不必要检查。还可以重新排列代码段,从而能够将最常用的路径排在一起,而不是强制要求在代码内进行不必要的跳转。这避免了开销很高的转换检测缓冲器(Translation Lookaside Buffer,TLB)颠簸和页面调度(paging)。

可以将不常使用的代码放在该模块的一个特定的部分中,这也有助于避免上述问题。可以执行虚拟调用的途径,因为虚拟调用地点常常导致对某一特定的类型进行调用,从而能够避免常见情况中的虚表查找。可以执行局部内联,借此确保只对函数中经常使用的代码段进行内联,而且这种决策是基于每个调用地点做出的。此外,某些代码段会以某种优化为目的进行编译,而其他代码段的编译则有着不同的目标。例如,经常使用的和/或小的函数可以编译为最大化速度(/O2);而不经常使用和/或大一些的函数会被编译为最小化空间(/O1)。

如果能够了解实际情况,并且能够将应用程序投入经常使用的情况,而与此同时还进行一些规范化的工作,则应用程序的性能将会得到极大的提高。最近,使用POGO对SQL Server进行了重新编译,并且在许多常见的情况下,获得了30%的性能飙升。这样下去,可以断定,Microsoft会开始使用这一技术来将它的许多产品进行编译。需要注意的是,在分析规范构建时,不要试图涵盖全部的代码,这一点非常重要。POGO的全部意义在于确定如何优化常见的用例。如果试图涵盖全部的代码,将得到深刻的教训。

Visual C++ 2005还增加了对OpenMP的支持,它是一个构建多线程程序的开放规范。它由一组程序组成,用来指示编译器代码的哪些部分可以平行放置。如果代码具有大的循环并且这种循环与前面的迭代没有依赖关系,则这种代码最适合使用OpenMP。看一看下面这个简单的copy函数,它将数组a和b中的值相加,并将结果存储在c中:
void copy(int a[], int b[], int c[], int length)
{
    #pragma omp parallel
    for(int i=0; i     {
        c[i] = a[i] + b[i];
    }
}

在拥有多个处理器的机器上,编译器会生成多线程来执行这个循环的迭代,而每个线程将执行复制操作的一部分。需要注意的是,编译器无法验证循环是否具有依赖性,因此它不会阻止在不适当的情况下使用这些代码。如果具有依赖性,就极有可能得到与想像的不同的错误结果,尽管它们在规范方面是正确的。

虽然使用OpenMP的最大好处常常来源于平行放置循环(如刚才的例子所示),但是在直线型代码中使用它也会使性能得到改善。“#pragma omp section”指令可以用来区分一段代码中的非依赖性部分,这样就可以让开发人员指定可以并行运行的区域。然后,编译器就可以产生多线程,从而在不同的处理器上执行这些代码段。

对于使用.NET的开发人员来说,一个重要的改变是,Visual C++ 2005优化器对中间代码做出的优化和对本机平台做出的优化大体上是一样的,尽管优化器是通过不同的调优来做到这一点的。而现在的JIT编译器是在运行时分析并优化的,它允许C++编译器在初次编译时就进行优化,这样也可以提供极大的性能优势(C++编译器就可以有更多的时间来进行分析而不是保持JIT)。Visual C++ 2005编译器首次对托管类型进行了优化:执行循环优化、表达式优化和内联。但是在有些地方编译器是无法对基于.NET的代码进行优化的。例如,由于指针算术运算的无法验证性,它就有一些无能为力了,而且某些代码由于CLR对类型和成员可访问性的严格要求而无法内联,尽管编译器的确对合法的内联机会进行了大量的分析。此外,优化中间代码需要平衡考虑对JIT编译器的影响。例如,不可能去解开一个循环而暴露过多的变量给JIT编译器,因此,JIT编译器必须进行注册表分配(一个NP完成问题)。Visual C++小组正在对这些问题进行研究,并在系统发布时会得到一个经过良好调优的优化解决方案。

安全性
在2002年,Bill Gates发出了可信任计算(Trustworthy Computing)倡议,这对Microsoft开发的所有产品都有着不可小视的冲击力。Windows操作系统的开发人员在安全性培训和代码评审上花费了几个月的时间,这使得Windows Server 2003成为该公司曾经发布过的安全性最高的一个操作系统。Microsoft Office 2003还包含了许多安全功能,例如Information Rights Management (IRM)、更好的宏安全性和在Outlook中阻止HTML下载等等。而编译器小组也在大踏步地使他们开发的编译器及其生成的代码变得更安全。Visual Studio .NET 2002引入了缓冲区安全检查/GS编译器选项。这一标志将导致编译器在决定为有缓冲区溢出攻击嫌疑的函数返回地址之前就预先在堆栈上分配空间。在函数进入时,一个带有已知计算值的安全Cookie会被放在这个缓冲区中,而在函数退出时,编译器会对其进行检查,以确保该Cookie没有被破坏。对Cookie值的更改意味着有重写返回地址的可能性,而这会产生一个错误并导致应用程序终止。当然,这并不能防止所有的缓冲区溢出攻击。Visual Studio .NET 2003还增强了/GS功能。它通过对堆栈上的局部变量进行排序来将数组分配在高于其余局部变量的内存地址上,从而防止这些局部变量造成溢出。这样会阻止基于vtable入侵的攻击和其他基于指针的攻击。

Visual C++ 2005对这一强大的功能进行了又一次升级。当进行函数调用时,函数的激活记录按照如图7所示的方式放置。如果一个局部缓冲区发生了溢出,黑客就有可能重写其堆栈上的所有内容,其中包括异常处理函数记录、安全cookie、帧指针、返回地址,以及函数的参数。这些值中的大多数都是通过各种机制保护的(例如安全异常处理),然而,在一个以函数指针作为参数的函数中,利用缓冲区溢出仍有可能。如果一个函数将函数指针(或者一个包含函数指针的结构或类)作为参数,黑客就可能会重写这一指针的值,并使代码执行任何他想要运行的函数。为了防止这一点,Visual C++ 2005编译器会分析所有的函数参数来防止这一漏洞。那些易受攻击的参数会在堆栈的局部变量下创建副本,然后使用这些副本而不是参数本身。缓冲区溢出可能会重写原始值,但是它们不会被利用,因为正在使用的副本仍然保持原始值。

与“缺省安全(secure by default)”可信任计算指令相一致的是,Visual C++ 2005编译器现在默认支持缓冲区安全检查选项。这会有助于使所有用Visual C++编译的产品更加安全。实际上,Microsoft现在正在用这个已经启用的选项来构建它的所有产品,其中包括Windows、Office和SQL Server。Visual C++ 2005在其他方面的进步就是确保代码是以考虑安全性为前提开发的。绝大多数C++应用程序都依赖于C运行时库(CRT)和标准模板库(STL)。这些库在最初设计时,代码安全性不具有高的优先级,而许多现在普遍使用的攻击也不存在。这样做的结果是,许多由这些库提供的功能常常以一种不安全的方式被使用,从而使得应用程序暴露在一些潜在的攻击之下。最近出版的书籍(如Michael Howard撰写的Writing Secure Code(Microsoft Press,2002年)中,都强调了某些编码实践的重要性,这些实践都不以这些库为例。在Visual C++ 2005中,Microsoft推出了经过重新编写的这些库的新版本,它致力于找出所有可能导致一般安全问题的函数,并提供一些更安全的替换版本。这项工作的长远目标是弃用所有“不安全”的版本,而提倡使用一些具有相同功能但更加健壮的版本。单单是CRT的这个新版本,就引入了超过400个新的“安全”函数,从而可以确保检查所有的指针参数是否为空值,并且所有进行内存复制操作的函数除了知道目的缓冲区和源缓冲区之外,还要知道需要复制多少字节的数据。

小结
Visual C++ 2005的新功能还有很多,难以在此一一详述:混合映像的延迟CLR加载、本机应用程序域 API、为在应用程序域和进程方面提供更好的全局变量支持而引入的新的声明规范、模块构造函数、为对象文件和.NET模块提供的链接器支持、隐式装箱、使用与C#开发人员所喜爱的语法一样的XML注释、全新的面向.NET框架的STL版本、参数数组、别名提示、新的浮点模型、运算符重载等等。

任何基于.NET框架的语言的新版本经常让人们想问“如果我们的开发小组想要编写一个面向.NET的应用程序,应该使用哪一种语言?”现在,如果您正在做许多本机互操作工作,那么很简单。C++是开发本机互操作最易于使用的语言,而且它常常具有最佳的性能。此外,如果您想要将现有的C++应用程序转到.NET上,那就的确没有更好的选择了。实际上,当您把现有的应用程序转化为.NET框架时,使用Visual C++是Microsoft极力推荐的一条途径。

至于新的应用程序,您或许会问,为什么不熟悉.NET的开发人员选择一种语言而不选择另一种语言。由于每种语言都有其优势,所以无法对这个问题作出简单的回答,但对于纯基于.NET的应用程序而言,C#、Visual Basic和C++中的体验基本相同。如果您作为一个开发人员已经习惯于使用某种特定的语言,就没有什么重要的原因要转向使用另外一种语言了。

但如果您正在开发某种互操作,您也许会选择C++语言而不是其他。使用C++体验肯定要好于其他的语言,因为在C++中直接内置了范围广泛的互操作支持。此外,它通过析构函数提供的确定性清除功能在消除资源泄漏和确保应用程序的正确性时简直就是无价之宝。C++还有许多强大的功能可以与CLR提供的功能联合使用。例如,C++不仅支持模板和泛型,而且还支持它们的组合。这比单独使用其中任何一个功能更富有表现力,也更为强大。尤其有用的一个库编写技术是编写实现一般接口的模板。这会为您的模板提供所有的灵活性和强大的功能(例如专用化),而它仍会让其他语言有通过一般接口直接使用从模板实例化的对象的能力。总而言之,C++的确找到了属于自己的位置。

 

    黑客防线网安服务器维护方案本篇连接:http://www.rongsen.com.cn/show-321-1.html
网站维护教程更新时间:2008-02-22 04:32:29  【打印此页】  【关闭
我要申请本站N点 | 黑客防线官网 |  
专业服务器维护及网站维护手工安全搭建环境,网站安全加固服务。黑客防线网安服务器维护基地招商进行中!QQ:29769479

footer  footer  footer  footer