Date: | September 8, 2006 / year-entry #306 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060908-03/?p=29803 |
Comments: | 19 |
Summary: | Consider this follow-up question to the question from last time: When I call the PropertySheet function, can I assume that the phpage field of the PROPSHEETHEADER structure will not be modified? If we take a look at the declaration of the PropertySheet function, we see that it reads like this: typedef const PROPSHEETHEADERA *LPCPROPSHEETHEADERA; typedef... |
Consider this follow-up question to the question from last time:
If we take a look at the declaration of the typedef const PROPSHEETHEADERA *LPCPROPSHEETHEADERA; typedef const PROPSHEETHEADERW *LPCPROPSHEETHEADERW; WINCOMMCTRLAPI INT_PTR WINAPI PropertySheetA(LPCPROPSHEETHEADERA); WINCOMMCTRLAPI INT_PTR WINAPI PropertySheetW(LPCPROPSHEETHEADERW);
Go past all the function declaration specification goo and look
at the parameter list.
It's a
One of the rules for const pointers is that you can read from them
but you cannot write to them.
Consequently, the |
Comments (19)
|
In ANSI C++, deleting an object is considered not to change it. So for example:
void kill( const int *p ) {
delete p;
}
will compile. It’s a bizarre rule that the standard added – it wasn’t true for ARM C++.
Dave
I don’t see anything bizarre about that. Surely we need to be able to delete pointers to const objects, else for any non-trivial app, we’d soon be running out of memory.
There probably wouldn’t be so many questions if Windows didn’t use such silly typedefs.
Given this prototype, is the argument const?
PropertySheetW(LPCPROPSHEETHEADERW);
The approved answer is “Well, of course, there’s a C in the type name”.
The prototype is a whole lot easier understand if it’s not willfully obfuscated:
PropertySheetW(const PROPSHEETHEADERW *);
This is readily understandable by any C programmer (well, except those who haven’t qute got round to understanding new-fangled concepts like ‘const’).
Tell me again why these LPCBANANA typedefs are a good idea?
Ray,
Why were they useful in 16-bit Windows?
And sorry for commenting 3 times in this blog entry – I hope that’s alright (once in a while).
Nish,
In Win16 days, you had 16 and 32-bit pointers. The "LP" stands for "Long Pointer". The typedefs were used to ensure that programs compiled in small and medium memory models were using right pointer types when calling API functions.
BTW: probably you should avoid "Ray"-ing here :)
“Assuming your code doesn’t modify the PROPSHEETHEADER yourself, any value on exit from the function will be the same as the value it had on entry.”
Not entirely true. If the PROPSHEETHEADER contains pointers to non-const other data (such as phpage), that data can be written to, even though the pointer itself can’t be.
In fact, AFAICT, “phpage” appears to be the only non-const pointer member of a PROPSHEETHEADER. So, while “phpage” can’t be made to point anywhere else, the data it points to *can* be modified. (But for any other member, your response would have been correct)
phpage
member will be unchanged. What it points to, however, is another matter. -Raymond]Unfortunately, not all Win32 API functions declare constness of their parameters accurately. Here’s just one example:
BOOL DrawDibDraw(
HDRAWDIB hdd, HDC hdc,
int xDst, int yDst, int dxDst, int dyDst,
LPBITMAPINFOHEADER lpbi,
LPVOID lpBits,
int xSrc, int ySrc, int dxSrc, int dySrc,
UINT wFlags
);
Surely drawing a DIB is not supposed to change it? Why, then, aren’t lpbi and lpBits declared as LPCBITMAPINFOHEADER and LPCVOID, respectively? There isn’t even such a thing as a LPCBITMAPINFOHEADER.
You might say it’s tradition, or backwards compatibility. But, didn’t all handle types use to all be the same type? Now, with STRICT defined, they are distinct types. Similarly, it should be possible to declare all constant pointers as such in STRICT mode. After all, it’s not like “const” means anything to the binary layout of parameters.
>>
typedef const PROPSHEETHEADERA *LPCPROPSHEETHEADERA;
typedef const PROPSHEETHEADERW *LPCPROPSHEETHEADERW;
It’s a const pointer to a PROPSHEETHEADER structure (either ANSI or Unicode, depending on which flavor of the PropertySheet function you’re calling).
<<
Hey Ray,
Isn’t that a pointer to a const PROPSHEETHEADER? The pointer itself is not const.
"One of the rules for const pointers is that you can read from them but you cannot write to them."
Since we’re being pedantic, you *can* write to a pointer to const. You cannot write *through* such a pointer to the data structure(s) it references.
Adrian’s right, and it’s a great point.
If you see a function taking a pointer-to-const something, you can’t assume that the structure won’t be modified. The const does not guarantee it, because that function may have a identical non-const pointer up its sleeve somewhere.
Or, it can simply cast away the const. Buh-bye!
Or, maybe another thread has a non-const pointer to it, and updated in between.
It’s more of a strong hint that the compiler can help enforce, than a guarantee.
The only that guarantee is… the documentation. We may be can able to say "PropertySheet" will not modify that structure, but we can’t extrapolate to all functions that take const pointers.
Thus, it’s a good question to ask: even though I see a const parameter, does function X respect it?
I find these things easier to understand when written and read from right to left.
// var is a pointer to a constant T
T const * var;
// var is a constant pointer to a T
T * const var;
// var is a constant pointer to a constant T
T const * const var;
I do agree that the LPCFOOBAR typedefs obfuscate this somewhat.
PMP
… the notorious (documented) exception being CreateFileW when the path is of the "\?<device><path>" variety, where the path is going to get converted in-place to "??<device><path>" and passed to the kernel (and then converted back)
<<Or, it can simply cast away the const. Buh-bye!>>
The const as a promise.
If I declare this to be a const, I promise you that I am not going to change it.
When I cast, I am telling the compiler "you shut up, I know better!"
It is not the compiler’s fault, it is mine, because I am breaking the contract.
The "const" keyword is clearly something of a random hit-or-miss afterthought in Win32 (and all its half-a-million-or-so COM interface functions now part of a basic Win32-anno-2001 programming interface). Things that clearly should be const are not, and forces casts and bad design decisions to be propagated into otherwise correct program and library code.
Sure, it can be of interest explaining to the ones not knowing the C language that "const means const", but of much greater interest to at least me is the glaring errors of MS interface designers where they didn’t even bother to try to be const correct – and if that’s a product of ignorance it’s just proof they shouldn’t have been allowed to be interface designers in the first place.
Just my 0.02.
Friday, September 08, 2006 11:31 AM by Adrian
> "One of the rules for const pointers is that
> you can read from them but you cannot write
> to them."
>
> Since we’re being pedantic, you *can* write
> to a pointer to const. You cannot write
> *through* such a pointer to the data
> structure(s) it references.
As a language rule that is partly true. An additional rule is that if the actual object really is defined as const then no you can’t write to the actual object even if you have created a pointer-to-nonconst that points to the object. (Of course the "can’t" might not be enforceable at compile time, and the result is undefined behaviour.)
Saturday, September 09, 2006 1:02 AM by KJK::Hyperion
> … the notorious (documented) exception
> being CreateFileW
I remembered that the API rule is different from the language rule in that way, but didn’t remember which function. Now I’m even more confused. Now I’m reading
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfile.asp
I do not see documentation of the notorious exception that you mention. Are you sure that the notorious exception is CreateFile and not some other function? I had a vague recollection that it involved a parsing function of some kind, writing a _T(‘