Reputation: 95
I was reading through 'Beginning OpenGL Game Programming Second Edition' and came across this struct definition:
typedef struct tagPIXELFORMATDESCRIPTOR
{
WORD nSize; // size of the structure
WORD nVersion; // always set to 1
DWORD dwFlags; // flags for pixel buffer properties
...
}
"The first of the more important fields in the structure is nSize. This field should always be set equal to the size of the structure, like this: pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); This is straightforward and is a common requirement for data structures that are passed as pointers. Often, a structure needs to know its size and how much memory has been allocated for it when performing various operations. A size field allows easy and accurate access to this information." (pg. 24)
Why does the struct need the user to pass the size to it? Can code that uses this struct not just use sizeof() when needed?
Upvotes: 8
Views: 1713
Reputation: 263217
In most cases, if you access tagPIXELFORMATDESCRIPTOR
using pointers of type tagPIXELFORMATDESCRIPTOR*
, you probably don't need a member to specify the size; sizeof
will always give you the correct size:
void func(tagPIXELFORMATDESCRIPTOR *ptr) {
// sizeof *ptr is the correct size
}
But if you play tricks involving using pointers of different types, probably using pointer casts, then a size member at the beginning of the structure can let you determine the structure's size without knowing its type.
For example, you might define a type that contains nothing but a size member:
struct empty {
WORD nSize;
};
Then, as long as you carefully set the nSize
member to the correct value for every object you create (and as long as nSize
is always at the same location within each structure), you can get the size of a structure without knowing its actual type:
void func(empty *ptr) {
// sizeof *ptr is incorrect
// ptr->nSize is the actual size (if you've done everything right)
// That's ok if we don't need any information other than the size
}
...
tagPIXELFORMATDESCRIPTOR obj;
...
func(reinterpret_cast<empty*>(ptr));
Which is not to say that this is a good idea.
If you can just use the appropriate pointer type, without doing pointer casts, you should do it.
If you can't, C++ provides much cleaner and more reliable ways (particularly inheritance) to define related types. It's far better to define what I've called empty
(or perhaps it should be called something like descriptor
) as a class, and then define tagPIXELFORMATDESCRIPTOR
as a subclass.
I'm not familiar with OpenGL, but I suspect it was originally designed to use C-style pseudo-inheritance. You might have to stick with that model if you need to work with OpenGL objects in your C or C++ code.
Upvotes: 1
Reputation: 7257
Imagine you are a developer creating Windows API. You have some set of API calls defined, documented and OS released. Many of your current API calls accept pointers to structs as input args to allow passing many input values without having huge amount of input args.
Now developers start to write code for your OS.
Few years later you decide to create new version of Windows OS. You have some requirements though:
OK - for your old programs to work your new API must have same routines with same args etc.
Now how to extend your API? You may add new API calls but what if at the same time - want to use your old code and use some new fancy features without many changes to your code?
Usually API routines require much info but it is inconvenient to create routines that have many formal arguments. That's why it is frequent that one of formal args is a apointer to a struct containing properties that you want to pass to your routine. This makes API extending easy. For example:
your older code:
struct abc
{
int magicMember; // ;-)
int a;
int b;
int c;
};
void someApiCall( struct abc *p, int blaBla );
Now if you decided to extend your 'someApiCall' by providing more info without changing routine's signature you just change your structure.
your new code:
// on new OS - defined in a header with the same name as older OS
// hence no includes changes
struct abc
{
int magicMember; // ;-)
int a;
int b;
int c;
int new_stuff_a;
int new_stuff_b;
};
void someApiCall( struct abc *p, int blaBla );
You've retained routine's signature and at the same time allowed both old and new code to work. The only secret is magicMember which you can treat as struct's revision number or - if in new versions you just add new members - size of struct. Both ways your 'someApiCall' will be able to distinguish between 2 types of 'the same' struct and you'll be able execute that API call from both old and new code.
If one is picky - (s)he may say these are not the same structures. Indeed thay are not. They just have the same name to prevent more code changes.
For real example check RegisterClassEx API call and WNDCLASSEX struct it takes
Upvotes: 1
Reputation: 320391
There are at least two possible reasons for that
The exact definition of the struct will change with time, as the library API that uses it develops. The new fields will be added at the end, changing the definition of the struct and changing its sizeof
. Yet the legacy code will still supply the "older" smaller struct to the same API functions. To make sure both the old and the new code works, run-time size information is necessary. Formally, this is what the nVersion
field can be used for. That field by itself should be sufficient to tell the API what version of the API the calling code expects to be using and how many fields it allocates in the struct. But just for extra security the size information might be supplied through an independent nSize
field, which is not a bad idea.
The struct contains optional or flexible information (regardless of the API version). The filling code will either decide what information you need or don't need based on that size or truncate the flexible-sized information based on the size you requested. This might be especially appropriate if the struct has a flexible array member at the end (along the lines of "struck hack" or such).
In this specific case (PIXELFORMATDESCRIPTOR
struct from Windows API) it is the first reason that applies, since there's nothing flexible in that struct and the related API.
Upvotes: 6
Reputation: 101456
Can code that uses this struct not just use sizeof() when needed?
That's the idea -- don't use sizeof
to determine the size of the message. This kind of structure is very prevalent in server programming when using communication over sockets, and it's also common in the WinAPI.
When a binary or fixed-width protocol is first developed, individual messages are defined with specific fields, each with an individual size. Client code that is reading these messages -- either off of a socket or some other kind of buffer used in inter-process communication -- need to know how much data to read for this message before moving on to the next message. This is especially true if multiple messages are sent in a single frame or buffer.
Consider if you get a populated buffer of data and there are three PIXELFORMATDESCRIPTOR
messages in it. If you know the size of each message you can correctly move from one message to the next when processing the buffer. How do you know the size of each message?
You can just use sizeof (PIXELFORMATDESCRIPTOR)
if you know that the size of the message will never change -- but there are at least three problems with this approach. First, even if the specs say that the size of a message will never change, sometimes they do anyway when the original developer changes his mind. This happens. Second, if your code was developed against one version of the spec and the server is sending messages based on another version of the spec, if the size of the message changes your sizeof
will no longer reflect the true size of the message on the wire, and very bad things will happen. Third, if the buffer includes a message you know nothing about in your code, there is nothing to do a sizeof
against, and you won't be able to process the rest of the buffer.
Checking sizeof
to determine the size of a message on the wire is not a sustainable approach. Better is to have the protocol tell you in real-time how big each message is, and process that many bytes from the buffer when parsing a message. If the size of the message is in the same place for every message type (which is a recommended practice when designing such protocols), then you can even correctly pull messages from the buffer that you know nothing about.
This approach also smoothes the upgrade path when the protocol changes. In my line of work there are several protocols I program against that don't include the size of the message on the wire. When these messages change, we have to do a "hot cut" of one client version to the next, coordinated with the exact time when the server is upgraded. Imagine the pain this causes when there are hundreds of servers scattered throuought the world that process this data. If the protocol sent the size of the message on the wire, then we could take a more measured approach to upgrading the client software as the server is upgraded -- even putting a new version of the client software in production either before or after the server was upgraded.
Upvotes: 3
Reputation: 57678
The size field can also tell the receiver how much memory to allocate for the structure.
This technique is commonly used for messages, especially in embedded systems and when copying of the message is necessary.
Upvotes: 1
Reputation: 308121
This allows the definition of the structure to change over time. As new fields are added at the end, the size field tells you which version to use.
Upvotes: 4