(一)描述符概述
Symbian自定义了一套系统底层解决方案用于处理字符串,它可以对内存溢出提供有效的保护。这套方案就是描述符。此外,Literal类型也是与字符串有关的类型,可以简单的将其理解为C语言中的static const char[]。Literal类型可以通过operator()方法轻松的转换成描述符。
描述符类族中提供多种类型的描述符,以满足各种需要,以下是描述符的类图:
其中TDesC、TDes、TBufBase和TBufCBase都是不可实例化的类,我们真正使用的描述符类型是最下面一层的各种类型。而下面这张表将我们熟悉的C语言中的字符串和描述符作了一个近似的对照,供我们参考:
下面是6种描述符和Literal类型的用法:
const TInt KHelloWorldLength = 13;
//Use Literal
_LIT(KHelloWorld, "Hello World!/n");
console->Printf(KHelloWorld);
//Use TBuf
TBuf<KHelloWorldLength> tbuf(KHelloWorld);
console->Printf(tbuf);
//Use TBufC
TBufC<KHelloWorldLength> tbufc(KHelloWorld);
console->Printf(tbufc);
//Use HBufC
HBufC* hbufc = KHelloWorld().AllocL();
console->Printf(*hbufc);
//USe RBuf
RBuf rbuf;
rbuf.CreateL(KHelloWorld, KHelloWorldLength);
CleanupClosePushL(rbuf);
console->Printf(rbuf);
CleanupStack::PopAndDestroy();
//Use TPtrC
TPtrC tptrc(tbufc);
console->Printf(tptrc);
//Use TPtr
TPtr tptr = hbufc->Des();
console->Printf(tptr);
注意:HBufC是在堆上分配空间的类型,因此如果HBufC在创建之后,出现有可能异常退出的操作,那么需要将HBufC压入清空栈。本例中由于console->Printf和hbufc->Des()均不会异常退出,因此没有压栈。RBuf属于R类,同理。(这里为了强调RBuf的用法写出了清除栈操作,实际上是没有必要的)。
下面是描述符内存布局示意图:
调试上面代码可以在Carbide中看到iLength的十进制值,iLength是一个32位的TInt型值,它的最高4位表示存储描述符内存布局的类型,剩下的位表示描述符长度。也就是上图中Type和Length连接在一起组成的一个十进制数字。
为什么我们需要Type值:TDesC::Ptr()方法实际上是通过switch语句来识别描述符内存布局类型的,即通过前四位的Type硬性编码来识别,这样做是为了避免具体描述符作为TDesC或TDes参数传递时使用虚函数(虚函数对于性能开销较大)。通过Type值,TDesC就能知道子类的内存分布和类型而无需通过虚函数调用。
(二)栈描述符TBuf和TBufC
TBuf和TBufC都是模板化的数组,创建时采用一个整数值指定长度,TBuf可以在创建后在末尾继续Append数据,但TBufC不能直接Append,可以通过Des(),返回一个TPtr指针再进行Append操作。
TBufC比TBuf节省4字节(32位)的空间,因为它无需存储最大长度。但如果需要频繁修改TBufC,就需要调用Des(),这样又会产生性能上的开销,因此在大量使用TBuf和TBufC时我们需要权衡这两者的使用情况。
(三)指针描述符TPtr和TPtrC
注意:TPtr和TPtrC虽然都是指针,但它们不负责释放它们所指向的数据。
(1)TPtrC指向不同类型的数据:
// 1 Construction of a TPtrC from a literal
// ptr points to "Hello" in the program binary
_LIT(KTxtHello, "Hello");
TPtrC ptr(KTxtHello);
// 2 Setting a TPtrC to point to data in a stack descriptor
// ptr points to "World" in the buf descriptor variable
_LIT(KTxtWorld, "World");
TBufC<5> buf(KTxtWorld);
ptr.Set(buf);
// 3 TPtrC set to point to raw data held in a C-style array
// ptr points to "Hello" in the array variable
const TText array[6] = L"Hello";
ptr.Set(array, 5);
// 4 TPtrC set to point to a portion of data held in a descriptor
// ptr points to "or" in the buf descriptor variable
ptr.Set(buf.Mid(1,2));
(2)TPtr有两种类型:2(指向数据,数据可以在也可以不在描述符中)和4(指向描述符)。
TPtr类型2:所有的TPtr都是类型2,除非它们用HBufC::Des()或TBufC::Des()创建。
TText cStyleArray[] = L"Hello";4
// Creates a pointer to the array of data. The 2nd parameter is
// the length of the data pointed to and the 3rd parameter is
// the maximum length of the data pointed to.
// sizeof(cStyleArray) returns 12 which is the number of bytes,
// however the conceptual length of the data is 6, hence the
// division by 2.
TPtr ptrToRawData(cStyleArray, sizeof(cStyleArray)/2,
sizeof(cStyleArray)/2);
// copy "World" into the cStyleArray memory location
_LIT(KTxtWorld, "World");
ptrToRawData = KTxtWorld; // cStyleArray now contains "World"
// TPtrs provide safety when writing to raw memory as an attempt
// to write beyond the end of the data storage area results in
// a panic
ptrToRawData += KTxtWorld; // this will panic!
TPtr类型4:类型4的TPtr只能通过HBufC::Des()或TBufC::Des()创建。
注意:如果有多余一个的类型4 TPtr指向同一HBufC或TBufC,通过一个TPtr做的修改,不会在另一个中体现出来。
_LIT(KTxtHello, "Hello");
_LIT(KTxtString, "Overwrite me");
HBufC* hbuf = KTxtString().AllocL();
// hbuf contains "Overwrite me"
TPtr ptr = hbuf->Des(); // points to the hbuf contents
ptr = KTxtHello; // hbuf now contains "Hello"
delete hbuf;
(3)函数Set()
Set用于改变TPtr和TPtrC指向的内容。
_LIT(KHello, "Hello");
_LIT(KWorld, "World");
TPtrC hello(KHello); // contains "Hello"
TPtrC world(KWorld); // contains "World"
world.Set(hello); // world now contains "Hello"
声明TPtr并初始化为空:
TPtr ptr(NULL, 0);
. . .
ptr.Set(hbuf->Des());
TPtr作为类成员,在构造函数中初始化为空:
CSomeClass::CSomeClass() : iPtr(NULL, 0)
{}
. . .
iPtr.Set(hbuf->Des());
(四)堆描述符HBufC和RBuf
(1)HBufC的内存布局:
HBufC在堆中的分配以堆单元粒度为单位(Symbian系统指定的粒度),每当分配时,OS会自动调整HBufC的最大长度到下一个粒度边界,因此我们创建的HBufC多是比我们预期的要长。
注意:HBufC的长度数据没有保存在栈中,而是保存在对应的堆数据单元上。这样做是为了节省栈空间。
HBufC的创建:
_LIT(KTxtHello, "Hello");
HBufC* buffer = HBufC::NewL(32);
*buffer = KTxtHello;
//Another more efficient method to create HBufC
//HBufC* buffer = KTxtHello().AllocL();
delete buffer;
HBufC的重新分配:
可以通过ReAllocL重新分配HBufC,但如果有TPtr指向HBufC,则TPtr必须被重置,因为重新分配之后,HBufC会指向新的内存单元。
_LIT(KTxtHello, "Hello");
_LIT(KTxtGreeting, "How are you?");
HBufC* heapBuf = KTxtHello().AllocLC();
// place it on the cleanup stack
TPtr ptr(heapBuf->Des());
// ptr = KTxtGreeting; // would panic because the
// maximum length is exceeded
heapBuf = heapBuf->ReAllocL(KTxtGreeting().Length());
// So need to reallocate the heapBuf variable
CleanupStack::Pop();
// The address of heapBuf may have changed during ReAllocL
CleanupStack::PushL(heapBuf);
// so need to re-push it to the cleanup stack
ptr.Set(heapBuf->Des()); // and reset the pointer
ptr = KTxtGreeting;
// Now the assignment can be made safely
. . .
CleanupStack::PopAndDestroy();
(2)RBuf的内存布局:RBuf有两种类型:2(指向内存数据)和4(指向HBufC)
RBuf的创建:
RBuf myRBuf;
TBufC<20> myTBufC (_L("Descriptor data"));
myRBuf.CreateL(myTBufC);
或者
RBuf myRBuf;
_LIT(KGenesis, "In principio creavit Deus caelum et terram.");
TInt maxSizeOfData = 30;
myRBuf.CreateL(KGenesis(), maxSizeOfData); // trims text to 30 characters
RBuf的重新分配:(需要注意入清除栈和出栈的操作)
myRBuf.CleanupClosePushL();
. . .
const TInt newLength = myRBuf.Length() + appendBuf.Length();
if (myRBuf.MaxLength() < newLength)
{
myRBuf.ReAlloc(newLength);
}
myRBuf.Append(appendBuf);
. . .
CleanupStack::PopAndDestroy(); // calls myRBuf.Close();