Click here to Skip to main content
Click here to Skip to main content

Guide to using Pinning Pointers

, 30 Nov 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
Article on the usage and dangers of pinning pointers

Introduction

Having written an article on interior pointers, I thought the next logical topic for an article from me should be pinning pointers. This article will explain what pinning pointers are, when they come in handy and how they can be harmful to the overall performance of your .NET application. If you haven't already read my article on interior pointers and if you don't know what interior pointers are, then it'd be a good idea to quickly go through that article :-

Now that I've enjoyed the rather conceited satisfaction of having plugged my own article, lets get going.

What are pinning pointers?

During Garbage Collection cycles, the GC frequently compacts the CLI heap, relocates objects and adjusts managed references so that they continue referencing the same object that they were before the GC cycle. Since this is done transparently, the programmer needn't really be bothered with this at all unless there is an unmanaged pointer pointing to a CLI heap object. Since the GC cannot adjust unmanaged pointers, this would obviously result in havoc and so unmanaged pointers are not allowed to point to CLI objects. But there might be situations where you want to pass a CLI object to an unmanaged function (possibly during native interop) and to facilitate this, the CLR provides us with pinning pointers. A pinning pointer (or a pinned pointer) prevents the GC from relocating the pinned object for as long as the pinning pointer remains in scope. It's not difficult to write some arguably contorted code to verify this and we'll do just that :-

See below our test ref class, the DoLotsOfAllocs function that we use to fill up the CLI heap and to force a GC cycle, and a native function that takes an unmanaged int* as argument.

ref class Test
{
public:
    int m_i;
};

void DoLotsOfAllocs(int x=10000)
{
    for(int i=0; i<x; i++)
    {
        gcnew Test();
    }    
}

void NativeFunc(int* pInt)
{
    *pInt += 100;
}

Here's the test code :-

void _tmain()
{
    //Fill up Generation-0
    DoLotsOfAllocs();

    Test^ t1 = gcnew Test();
    //Need a gap between the two Test objects to avoid
    //the interior pointer being pinned too (the GC seems
    //to pin an area and not just an object)
    DoLotsOfAllocs(1000); 
    Test^ t2 = gcnew Test();

    pin_ptr<int> p1 = &t1->m_i;
    interior_ptr<int> p2 = &t2->m_i;

    NativeFunc(p1);
    Console::WriteLine(t1->m_i);

    printf("pinned p1 = %p : interior ptr p2 = %p\r\n",p1,p2);
    DoLotsOfAllocs(); //Force a Gen-0 GC
    printf("pinned p1 = %p : interior ptr p2 = %p\r\n",p1,p2);

Here's the output I got (obviously, addresses won't match on other machines) :-

100
pinned p1 = 00ACA244 : interior ptr p2 = 00ACD130
pinned p1 = 00ACA244 : interior ptr p2 = 00AA8D54

p1 (the pinned pointer) retains its value even after a GC cycle, while p2 (an interior pointer) has changed confirming that while the unpinned object has been relocated, the pinned object has remained fixed in the CLI heap.

Passing to native code

If all we needed to do was directly access the underlying object via a pointer and do pointer operations (like pointer arithmetic and comparison), then it'd wholly suffice to purely use interior pointers. But interior pointers do not convert to native pointers and thus cannot be passed to native functions expecting native pointers. That's where pinned pointers come in handy with their implicit conversion to native pointers.

//Native function
#pragma unmanaged
void Test2(wchar_t* p)
{
    wchar_t vowarr[] = L"aeiou";
    while(*p++ = wcschr(vowarr,*p) ? towupper(*p) : *p);
}

//Mixed mode function
#pragma managed
void Test1()
{
    String^ s = "hello world";
    pin_ptr<Char> p = const_cast< interior_ptr<Char> >(PtrToStringChars(s));
    Test2(p);
    Console::WriteLine(s);
}

PtrToStringChars returns a const interior pointer which we first const_cast to a volatile interior pointer which gets implicitly converted to a pinned pointer. Now we pass this pinned pointer to Test2(...) which accepts an unmanaged wchar_t* and the pinned pointer is implicitly converted to the unmanaged pointer.

Now that I've shown you how pinned pointers have advantages and uses over interior pointers, I've got to tell you this - only use pinned pointers when you have to and even when you have to, make sure that your pinned pointer does not remain in scope for too long. This is explained in the next section.

Pinning pointers and heap fragmentation

Lets assume that we have 3 objects O1, O2 and O3 in the CLI heap [assume Gen-0], which will now look like this :-

O1 O2 O3 Free Space

Now, assume that O2 is now an unreachable object and a GC occurs. After GC the CLI heap will look like this :-

O1 O3 Free Space

Now assume that O3 is a pinned object (means there are 1 or more pinning pointers pointing to it). If so, since the GC cannot relocate O3, the CLI heap will look like this :-

O1   O3 Free Space

Now imagine what happens if we had numerous such pinned objects (indicated by a gray background); the CLI heap would look something like :-

O1   O3   O5   O6 O7   O8 Free Space

Now, the heap is thoroughly fragmented, which makes GC cycles and memory allocation for new objects rather slower and less efficient than otherwise, and there is also the risk that there won't be a large enough contiguous block of free space to allocate a large object (say an array) which will result in some kind of memory exception.

Recommendations for using pinning pointers

[ Please note that these are strictly my ideas and could be remarkably erroneous for all I know, but with feedback from some of you out there who might know better, I hope to narrow down the level of inaccuracy. ]

  1. Use pinning pointers only when absolutely necessary, else try and use interior pointers or tracking references.

  2. When you do use pinning pointers, try and reduce the duration of their scope (the longer they remain in scope, the greater the risk of heap fragmentation).

  3. Try to avoid pinning generation-0 objects (since GC cycles occur most frequently in generation-0).

  4. If you need to pin multiple objects, try and allocate those objects together, so that the pinned objects will all be in one contiguous area in the CLI heap and the extent of fragmentation will be reduced.

  5. During interop calls, first check and see if the marshalling layer does any pinning for you, and do your own pinning only if required.

  6. Avoid situations where you pass a pinned pointer to a native pointer and risk using the native pointer after the pinned pointer has gone out of scope. Once the pinned pointer is out of scope, the object is no longer pinned and might get relocated or collected by the GC, in which case your native pointer is now pointing to a random area in the CLI heap.

Conclusion

I thoroughly enjoyed writing this article and am feeling rather proud of the fancy html-table graphics that I used for my CLI heap representations. If any of you feel that the graphics sucked, please don't let me know, so I can continue basking in an idiotic glory that makes sense only to me. On a more serious note, please feel free to submit you unmitigated feedback so that I can improve the article to the maximum extent possible. Thank you.

Related links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nish Sivakumar

United States United States
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Comments and Discussions

 
QuestionGreat Articel. A quick question for pinning a struct PinmemberMember 820080130-Aug-11 10:18 
AnswerRe: Great Articel. A quick question for pinning a struct PinmvpNishant Sivakumar30-Aug-11 10:20 
GeneralRe: Great Articel. A quick question for pinning a struct PinmemberMember 820080130-Aug-11 10:45 
GeneralPointers to native functions Pinmembergs178919-Jul-06 7:05 
GeneralGreat article Pinmemberluis.filipe.sousa20-Jun-06 0:59 
GeneralDoLotsOfAllocs.. C'mon, use the GC class :) PinmemberRGabo31-Jan-06 7:53 
GeneralRe: DoLotsOfAllocs.. C'mon, use the GC class :) PinstaffNishant Sivakumar31-Jan-06 8:05 
RGabo wrote:
Why don't you just do a GC::Collect()?

 
It's not guaranteed to do a collection. Though the docs are vague on that.
 
Regards,
Nish
 

GeneralI gave you a five... PinmemberJim Crafton1-Dec-04 9:19 
GeneralRe: I gave you a five... PinstaffNishant S1-Dec-04 16:43 
GeneralRe: I gave you a five... PinmemberNemanja Trifunovic4-Dec-04 7:44 
GeneralRe: I gave you a five... PinmemberJim Crafton4-Dec-04 17:40 
GeneralRe: I gave you a five... PinmemberNemanja Trifunovic6-Dec-04 3:33 
GeneralRe: I gave you a five... PinstaffNishant S25-Apr-05 18:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411022.1 | Last Updated 30 Nov 2004
Article Copyright 2004 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid