内部指针的两面性
在本文中,我将解释为什么使用本地指针来管理对象。原因在于对象是以垃圾收集器移动的。所以,当对象移动时,本地指针就变成无效的。所以,如果你想取得一个托管对象的本地指针,你就会遇到一个编译错。下面的代码显示了这上点。
using namespace System;
int _tmain()
{
int ^ hnd = gcnew int(100);
int* np = &hnd; // Genarates a compile error
}
但是本地指针还是非常有用的,如在使用指针算术和指针比较时就有许多的优点。所以新的C++ CLI允许你使用一个内部指针实现同样的功能。内部指针是本地指针的一个超集。所以它能够做任何内部指针所能做的一切。 但是当垃圾收集器移动指针所指向的对象时,内部指针也帮助程序实现其指向的地址的更新。
现在让我们看一下如何用内部指针来实现指针算术运算。
#include "stdafx.h"
#include <stdio.h>
using namespace System;
using namespace stdcli::language;
int _tmain()
{
const int SIZE = 10;
array<int>^ arr = gcnew array<int>(SIZE);
for(int idx = 0; idx < SIZE; idx++)
{
arr[idx] = idx + 1;
}
// 取得指向数组第一个元素的指针
interior_ptr p = &arr[0];
// 通过增加指针值读取并输出数组元素
for(int idx = 0; idx < 10; idx++)
{
printf("Value of the element at %Xh ", p);
Console::WriteLine(" is {0}",*(p++));
}
Console::ReadLine();
}
不管垃圾收集器执行多少次和数组元素移动多少次,上面的代码仍然能工作良好。于是C++的力量又回到了.net运行时刻库上。但是要小心使用内部指针。这些指针与本地指针非常相似,当试图存取不允许操作的内存区段时能给程序造成危害。例如,如果我在上面的代码中试图存取下一个p++,它将返回恰好在上面数组元素上方4字节的内存段位置。
传递托管堆地址
新的C++特点平衡了把托管堆的地址传递到非托管代码的能力。早期我们遇到的最大问题是,在托管堆中的对象的位置是非静态的。垃圾收集器以变化的时间间隔移动对象。现在新的pin_ptr(别针型指针)的引入禁止垃圾收集器改变在堆上的对象的地址。
下面代码展示了别针型指针的应用。
#pragma unmanaged
//本机函数,以整型指针作参数,执行计算
void calc(int* val)
{
//执行计算操作
}
#pragma managed
//托管函数调用本机calc函数
int managedfunc()
{
int i=gcnew int(10);
//把i的地址设置为别针型,以阻止对i的移动
pin_ptr<int> ppi=&i;
int* np=ppi;
calc(np);//用别针型int的地址调用本机函数
//把别针型指针的值置为nullptr,脱离了gc的限制
ppi=nullptr;
//完成剩下的工作
return i;
}
基于轮廓的优化(Profile Guided Optimization)
在新版本的托管C++中有一项新增功能称为基于轮廓的优化。这一新的编译特点能够实现在编译时刻把探针注入到代码中。最后的exe文件与一个数据库一起打包发送,由该库记录下注入代码中的探针监测到的结果数据。当用户运行该程序时,这些探针记录下应用程序的使用。当你下一次再编译时,编译器作出智能性决策,如根据探针的记录作为相应的动作。这一特性带来了真实世界的优化。
为什么没有为使用托管类型而定义头文件?
在传统的C++中,一个头文件一般包含对象的接口,或者类与函数的声明,等等。这允许在多个翻译单元上实现某种类型的一致性声明。对于一个用托管代码编写的且其被编译成MSIL的对象,需要在配件集中包含描述对象的元数据。因而,托管C++编译器不是通过头文件,而是通过使用配件集元数据来实现接口的读取。这就是为什么你不必因使用托管类型而包含头文件。事实上,你可以通过使用#using指令来让编译器从配件集中读取元数据。