Purpose of Pointers, a more in depth look

Started by
28 comments, last by daviangel 15 years, 11 months ago
Major uses of pointers in C/C++:

They are used to implement many basic data structures that require "chains of references", such as linked lists, trees, and graphs.

They are used to refer to objects that are dynamically allocated on the heap with the malloc call (C) or the new operator (C++).

They are used to iterate through arrays and other "linear" data structures.

They are used to pass or return references to large objects to or from a function, where passing the whole object would be expensive.

They are sometimes passed to emulate "out" parameters in C, ie parameters that the called function is expected to modify.

In C++, they are used to invoke polymorphic behaviour. For example, assigning between type compatible pointers is safe; assigning objects by value can cause unintended slicing and other undesirable mutilation of your objects. And you can use pointers to store "collections of things", where the things are all from a common base class, and method calls are polymorphic and adapted to each class of object (although this pattern, although hyped in OO circles, is less common in C++ than you might think.

There's probably many more uses. They are essential; read enough C/C++ code and you'll see them everywhere.
Advertisement


I use them all the time in network (C) code to point to multiple message structures sent in one packet (using a char pointer with an offset and then casting it to a pointer of one of my many different message structs).

Similarly I use them in object attribute management where I maintain my own heap for each object (which is one BIG struct including the heap containing many secondary nodes -- like object attributes). Maintaining a local heap makes rolling the object in and out of memory and sending it to a different server processing node much simpler and faster (almost no serialization needed bacause all the internal pointers are based on offsets from the top of the objects heap)

I use pointers for the few custom script languages used in my simulations -- the parser reads char by char and in another its a bytecode interpretor that steps thru precompiled 'code' using byte pointers.

My project is rather CPU intensive and uses huge amounts of memory, so making data as compact as possible is important (more fits, less to transmit, fewer cache misses). Higher level constructs would be wasteful and far slower and my heavy use of pointers allows me do do exactly what I need done without overhead of more generalized solutions.



--------------------------------------------[size="1"]Ratings are Opinion, not Fact
Quote:Original post by Valderman
Quote:Original post by ToohrVyk
Quote:Original post by Valderman
I don't know why Stroustrup would say such a thing, but it's just plain wrong.


It's perfectly correct, as per the standard, 4.10 §1. The basic idea is that the compile-time integer constant 0 gets converted by the compiler to a null pointer when evaluated in a pointer context (such as ptr == 0).
It still makes for less readable code, and all compilers might not be in on that part of the standard. Both MSVC and GCC sometimes have interesting interpretations of these things.


You should check what you say before you say it. In msvc I see

#define NULL 0

and I have never used the NULL macro, using it seems overly verbose and as macros including NULL are not pretty and the convention of ALL_CAPS is meant to draw your attention to them, I agree that they're best avoided unless absolutely needed.

The days of blaming standards compliance are essentially gone for C++98. The big players pretty much have it down really well.
Plus, since NULL is a macro, you need to #include <cstddef> in order to even use it.
just showing what i know of pointers:

pointers, point to a values MEMORY ADDRESS.
changing it will change the value held in that address, therefore saving space.

I've used pointers to have users change class data. It's a fairly powerful tool.
Quote:
pointers, point to a values MEMORY ADDRESS.

No, pointers point to things. The value of the pointer itself is the address of that thing.

Quote:
changing it will change the value held in that address, therefore saving space.

It takes no more or less space to change a value through a pointer than it does to change the value directly.

You're getting confusing with the fact that pointers are a means of providing referential semantics, particularly when passing parameters to functions. This allows you to refer to some existing object without copying that entire object onto to the stack as is typical of regular passing semantics (pass by value). This does save space, but is by no means a general trait of pointers.
Quote:Original post by LordShade
I have another good case for pointers. Let's say you have 3 structures you're written to a file in their binary form. Then you can just load the file into memory and munge your pointers to the proper index.

char* pLoadedFileBuffer;STRUCT1* pStruct1;STRUCT2* pStruct2;STRUCT3* pStruct3;pStruct1 = reinterpret_cast<STRUCT1*>(pLoadedFileBuffer);pStruct2 = reinterpret_cast<STRUCT2*>(pLoadedFileBuffer[SIZEOF(STRUCT1)]);pStruct3 = reinterpret_cast<STRUCT3*>(pLoadedFileBuffer[SIZEOF(STRUCT1) + SIZEOF(STRUCT2)]);


Voila! You have your structs all back without having to write routines for loading each individual item.


This is a quite bad way of doing things. For one, if your structures have any kind of alignment requirement, this can easily crash, because the representations-of-struct-contents in the buffer might not be aligned the way the structs need to be. Second, any time you want to "slurp" a data structure like that in one piece, you have to be aware of alignment (again), structure padding and endianness issues. Finally, the pointed-at structures don't actually exist; your ability to use the alleged structs is tied to the lifetime of the buffer.
fixed the code example....

pStruct1 = reinterpret_cast<STRUCT1*>(pLoadedFileBuffer);pStruct2 = reinterpret_cast<STRUCT2*>(pLoadedFileBuffer+SIZEOF(STRUCT1));pStruct3 = reinterpret_cast<STRUCT3*>(pLoadedFileBuffer+SIZEOF(STRUCT1) + SIZEOF(STRUCT2));


Quote:Original post by Zahlman
This is a quite bad way of doing things. For one, if your structures have any kind of alignment requirement, this can easily crash, because the representations-of-struct-contents in the buffer might not be aligned the way the structs need to be. Second, any time you want to "slurp" a data structure like that in one piece, you have to be aware of alignment (again), structure padding and endianness issues. Finally, the pointed-at structures don't actually exist; your ability to use the alleged structs is tied to the lifetime of the buffer.


I disagree with your sentiments here. Yes you do have to be careful with alignment (especially where SSE or MMX are used), yes you have to be careful about virtual tables, yes you have to be careful of a compilers padding, but for fast file IO it's certainly worth the effort. You can get around many of the alignment problems by using an asset compiler that runs on the target platform - then the structures will be correctly padded when you do an fwrite. Then the only problem you'll have is to make sure you align the (single) memory allocation.

Who really cares that the ability to use the structs is tied to the lifetime of the buffer? That's a bit like saying don't use new because the allocated objects returned will be invalid after you've called delete. That's not a reason against their usage, just a caveat you have to be aware of.
Quote:Original post by RobTheBloke
I disagree with your sentiments here. Yes you do have to be careful with alignment (especially where SSE or MMX are used), yes you have to be careful about virtual tables, yes you have to be careful of a compilers padding, but for fast file IO it's certainly worth the effort.


Do you really think it will be that much faster? Consider that stream objects buffer things internally anyway. Do you think fast file I/O will matter that much? Consider that actually grabbing a page of data from disk is usually the bottleneck in I/O operations.

Quote:Who really cares that the ability to use the structs is tied to the lifetime of the buffer? That's a bit like saying don't use new because the allocated objects returned will be invalid after you've called delete. That's not a reason against their usage, just a caveat you have to be aware of.


It's an unnecessary caveat: the natural ways of loading the structures will make them into separate objects automatically. If you want the objects to persist beyond the end of the function, you'll either have to make the buffer global or dynamically allocated (ugh) or copy them out somewhere explicitly (defeating the original purpose).
Quote:Original post by Zahlman
Quote:Original post by LordShade
I have another good case for pointers. Let's say you have 3 structures you're written to a file in their binary form. Then you can just load the file into memory and munge your pointers to the proper index.

char* pLoadedFileBuffer;STRUCT1* pStruct1;STRUCT2* pStruct2;STRUCT3* pStruct3;pStruct1 = reinterpret_cast<STRUCT1*>(pLoadedFileBuffer);pStruct2 = reinterpret_cast<STRUCT2*>(pLoadedFileBuffer[SIZEOF(STRUCT1)]);pStruct3 = reinterpret_cast<STRUCT3*>(pLoadedFileBuffer[SIZEOF(STRUCT1) + SIZEOF(STRUCT2)]);


Voila! You have your structs all back without having to write routines for loading each individual item.


This is a quite bad way of doing things. For one, if your structures have any kind of alignment requirement, this can easily crash, because the representations-of-struct-contents in the buffer might not be aligned the way the structs need to be. Second, any time you want to "slurp" a data structure like that in one piece, you have to be aware of alignment (again), structure padding and endianness issues. Finally, the pointed-at structures don't actually exist; your ability to use the alleged structs is tied to the lifetime of the buffer.

I guess I didn't list this in my previous list but I'm sure Stroustrup wouldn't approve since it clearly goes against AV Rule 182:
"
AV Rule 182 (MISRA Rule 45)
Type casting from any type to or from pointers shall not be used.
Rationale: This type of casting can lead to undefined or implementation-defined behavior (e.g. certain aspects of memory alignments are implementation-defined). Furthermore, converting a pointer to an integral type can result in the loss of information if the pointer can represent values larger than the integral type to which it is converted.
Exception 1: Casting from void* to T* is permissible. In this case, static_cast should be
used, but only if it is known that the object really is a T. Furthermore, such code should only occur in low level memory management routines.
Exception 2: Conversion of literals (i.e. hardware addresses) to pointers.
Device_register input = reinterpret_cast<Device_register>(0XFFA);
"
[size="2"]Don't talk about writing games, don't write design docs, don't spend your time on web boards. Sit in your house write 20 games when you complete them you will either want to do it the rest of your life or not * Andre Lamothe

This topic is closed to new replies.

Advertisement