Why can’t I use PSGUID_STORAGE like a GUID?


The stgprop.h header file defines a GUID called PSGUID_STORAGE, but a customer was having trouble using it.

    GUID guid;
    ...
    // This generates a strange compiler error
    if (IsEqualGUID(guid, PSGUID_STORAGE)) { ... }

The strange compiler error the customer referred to is the following:

test.cpp(136) : error C2143: syntax error : missing ')' before '{'
test.cpp(136) : error C2059: syntax error : ')'
test.cpp(136) : error C2143: syntax error : missing ';' before '{'
test.cpp(136) : error C2059: syntax error : '{'
test.cpp(136) : error C2059: syntax error : ')'
test.cpp(137) : error C2059: syntax error : '}'
test.cpp(137) : error C2143: syntax error : missing ';' before '}'
test.cpp(137) : error C2059: syntax error : '}'

"I don't see what the compiler is complaining about. The parentheses appear to be properly matched before the left brace."

Remember, what you see is not necessarily what the compiler sees. Let's take another look at this mysterious GUID:

#define PSGUID_STORAGE  { 0xb725f130,           \
                          0x47ef, 0x101a,       \
                          { 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac } }

Well there's your problem. After the preprocessor does its substitution, the line becomes

    if (IsEqualGUID(guid, { 0xb725f130,
              0x47ef, 0x101a,
              { 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac } })) { ... }

and that's not legal C/C++. (Though with a little tweaking, you can get GCC to accept it.) The PSGUID_STORAGE symbols is intended to be used as an initializer:

const GUID StorageGuid = PSGUID_STORAGE;

"How did you know that?"

I didn't, but I went to the effort of looking at the definition in the header file and figuring it out from inspection.

Why is it defined this way instead of

DEFINE_GUID(PSGUID_STORAGE, 0xb725f130, 0x47ef,
        0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac);

?

Because this GUID is used as the FMTID of a PROPERTY­KEY. The PROPERTY­KEY structure looks like this:

typedef struct {
  GUID  fmtid;
  DWORD pid;
} PROPERTYKEY;

The intended usage is evidently

const PROPERTYKEY
PKEY_STORAGE_DIRECTORY = { PSGUID_STORAGE, PID_STG_DIRECTORY };

Since the C language does not permit global variables to be initialized from other global variables (or at least it didn't at the time PROPERTY­KEYs were defined; who knows what crazy features will show up in C1X), PSGUID_STORAGE needs to be a macro which expands to an initializer rather than being a global variable.

Today's question was really just settling the prerequisites for tomorrow's topic. Stay tuned.

Comments (24)
  1. Anonymous says:

    If the compiler is complaining about syntax tokens that aren't there in the source code, then obviously a macro expansion is involved somehow (or sometimes an error in the most recently-included header file, like a lack of a semicolon after a class definition).

    Wikipedia doesn't mention anything about global initializers in C1X, but even if it will allow globals to be initialized from other globals, I wouldn't count on being able to use the feature anytime soon.  Heck, look at the status of C99 support these days, a 12-year-old standard.  Many C compiler vendors don't even try to be C99-compliant.

  2. Anonymous says:

    "How did you know that?"

    Because, after all, if it's not written down on MSDN or Stack Overflow, it cannot be determined using any technique known to mortal man.

  3. Joshua Ganes says:

    In a well-designed IDE, you may even be able to see the macro expansion with a simple mouse action. I am surprised at how many of my fellow developers fail to use the tools right in front of their noses. Usually, they just never knew you could do that.

  4. Anonymous says:

    @Anonymous Coward: Yeah I think gcc holds some special price for "most unhelpful compiler error" (not to say VC++ doesn't have its own set of problems, it certainly does). Especially with templates the chances of getting a even somewhat useful error are slim to nonexistant.

    I must say that clang is a positive surprise in this regard – they really put effort into this and it shows [1]

    [1] clang.llvm.org/diagnostics.html

  5. Anonymous says:

    @640k:  Insert parens and brackets "everywhere" and then suggest that to the developer?  Then the developer will take the suggestion, and the syntax might be right but the meaning will change, and the developer will blame the compiler writers assuming the developer ever notices that the wrong thing is happening.

    Excel does one thing that is helpful:  If you leave out the final ) on a formula, it will suggest that correction.  I don't think it tries to insert ( and ) everywhere to see if that fixes a syntax error!

  6. Anonymous says:

    @Voo: are you by any chance referring to the infamous ‘undefined reference to vtable’ error?

  7. Anonymous says:

    For some other examples of why the compiler shouldn't generally mess with the input, do some searching for javascript's semicolon insertion algorithm.  It can cause some apparently insignificant syntactic differences to have major semantic ramifications. It also causes problems for minimizers.

    One such example is here: robertnyman.com/…/beware-of-javascript-semicolon-insertion

  8. Anonymous says:

    Another bad example for semicolon insertion – if you're doing method chaining or string concatenation on multiple lines in JS, you have to put the dot or plus at the end of the line, rather than the start of the next.

  9. Anonymous says:

    @Anonymous Coward: I wasn't talking about one specific error, but if I had to name one I think I'd go with that one yes. I'm certain that I've never seen a more unhelpful, obscure, confusing compiler error ever – its only advantage is, that there aren't 3 pages of template errors. Also VC++ never had that problem which makes it all the more fun.

    Yep good call.

    clang is really godsend and I hope other compilers will take a note from it and improve as well – macro expanding would've made this error obvious. Although I think experienced c++ programmers tend to map certain output patterns to some specific error so one learns to live with it – but for beginners I now always recommend clang.. makes their life a good deal easier in my experience.

  10. Anonymous says:

    One of the main reasons behind the hate component of my love/hate-relationship with C++ derives from the fact that the compiler is apparently incapable of generating helpful error messages. Often this is because too many things are legal C++ and my error was the start of the correct syntax of something I didn't want; by the time the compiler figures out I screwed up it's past the error.

    But in this case, a bit more context could have saved a whole lot of confusion. Suppose the compiler would have said:

    test.cpp(136) : error C2143: syntax error : missing ')' before '{ 0xb725f130, 0x47ef, 0x101a, { …'

    That would have helped a lot, wouldn't it? And the ‘missing ')' ’ error is unspecific and misleading. It's the '{' that causes the problem, and ')' would have been illegal here since an expression-list cannot end in a comma, not even in C++0x / C++1x / whenever it's done. (Note: in C++0x braced-init-list can end in a comma.) So the compiler should have said:

    test.cpp(136) : error Cnnnn: syntax error : '{' invalid in expression before ' 0xb725f130, 0x47ef, 0x101a, { 0…'

    Or, in the case of C99 or GCC:

    test.cpp(136) : error Cnnnn: syntax error : cast required before initializer '{ 0xb725f130, 0x47ef, 0x101a, { …'

    P.S. The board software just ate my comment *and* disabled the back button so I couldn't go back and try to post it again. Please have someone fix that.

  11. Anonymous says:

    When code doesn't compile, a *good* compiler should try to insert (,),[,],{,} and/or other valid chars everywhere and see if it solves the errors. Then suggest this to the developer.

    Dumb compilers suggests stupid things that doesn't compile when following the recommendations in the error message.

    [The prescriptive approach comes with its own problems. -Raymond]
  12. Anonymous says:

    Please fix the syntax error in my leet code.

    I will accept your first suggestion, fire F7, and forget.

    {

       extern unsigned int x, y, z;

       return x < 10

               ?  y + 5 > 8  &&  (x < 4  ?  z << 4  :  2

               :  y & 3 / 2;

    }

    (Sorry, I reckon the original was just being humorous… couldn't resist)

  13. Anonymous says:

    @Simon Buchan: Lol, what a n00b! All the control flow statements, including <ReturnStatement>, don't allow semicolon insertion for that reason!

  14. Anonymous says:

    @voo: I'm sorry, but the error was obvious to me as soon as I saw it. And I don't do C/C++ profesionally, only on a hobby project with code from around 1998. In my mind the CAPITAL LETTERS triggered the 'it's a macro!' thought, and then the error makes sense.

    Or maybe it's just that using vim to write C/C++ makes on more used to having to think about what the errors mean instead of relying on an IDE to think for you.

  15. Anonymous says:

    @Drak: That works for ALL_UPPERCASE_MACROS, but what about the (sort-of-required for compatability) windows.h macros? I'll bet you wouldn't guess SetPort is a macro, for example.

  16. Anonymous says:

    @Drak You are right. I guess for all C/C++ programmers out there the error is really obvious (at least I hope so). To me it is strange that customers really come up with such hard to solve problems and say "Please MS fix my code I have no idea whats wrong". I am curious about to where Raymond is taking us tomorrow.

  17. Anonymous says:

    (Tail from Chris B's Javascript link): Incidentally it's possible to _comment-out_ the implied semicolon in Javascript as follows:

    return /*

    */ {

    javascript : "fantastic"

    };

    In my opinion this only makes the idea appear worse.

  18. Anonymous says:

    @Chris B, Joshua: That's not how semicolon insertion works: re ECMA-262v5, 7.9.1, semicolons are inserted before either a closing brace or the first token on a new line if and only if that token is not syntatically valid there. The problem with semicolon insertion is not where it puts them, but where it *doesn't* put them after you are used to skipping them:

    if (!authorized) return

    fireTheNukes();

  19. Anonymous says:

    @David Walker: Insert parens and brackets "everywhere" and then suggest that to the developer?  Then the developer will take the suggestion, and the syntax might be right but the meaning will change, and the developer will blame the compiler writers assuming the developer ever notices that the wrong thing is happening.

    That's why you should read, and fix, your warnings.

    Notice, if there's multiple ambiguous places where characters could be inserted automatically to fix the code, it should not compile. And of course the message should suggest fixing the code where the most common typos are likely.

  20. Anonymous says:

    @Drak You obviously missed the part where anonymous and I were talking about the undefined reference to vtable error in gcc (as the prime example of useless error messages produced by c++ compilers) – and if THAT one was obvious to you without any further information I'd be extremely surprised. So I assume you should use vim to to read these comments? :p

    Obviously a simple error as the above is trivial to trace back to a macro (never said anything else), although if you ever had to work on a project that uses lots of deeply nested macros you'll probably find it a lot easier to understand the actual problem by letting the preprocessor run over it and see the actual code (so why not let the compiler do it for you? saves work)

  21. Anonymous says:

    @Voo: Argh, I should have @Anonymous'd… He's the one that found the bracket error confusing. My bad! I've personally never seen a vtable error, so I'd probably freak out over it if it came my way :)

    @Simon: My hobby project is on Linux, and the previous creator of the code was nice enough to make all the macros in its header file in all caps. I sort-of presumed this was convention. (Shows I'm a hobby C-er, doesn't it :)

  22. Anonymous says:

    @Drak: Oh then you'd love that one. GCC even has a point about this error in their FAQ. To quote: "The ISO C++ Standard specifies that all virtual methods of a class that are not pure-virtual must be defined, but does not require any diagnostic for violations of this rule [class.virtual]/8. Based on this assumption, GCC will only emit the implicitly defined constructors, the assignment operator, the destructor and the virtual table of a class in the translation unit that defines its first such non-inline method."

    Or more humanly formulated: If you don't define at least one non-inlined virtual method, you'll get a linker(!) error bemoaning the missing vtable and such.

  23. How is this topic anything to do with the recycle bin?

    [It doesn't. But it's a prerequisite. Hint: In the first recycle bin article, there is a link from PSGUID_DISPLACED to this article. -Raymond]
  24. Anonymous says:

    "Well there's your problem. After the preprocessor does its substitution, the line becomes

       if (IsEqualGUID(guid, { 0xb725f130,

                 0x47ef, 0x101a,

                 { 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac } })) { … }

    and that's not legal C/C++. (Though with a little tweaking, you can get GCC to accept it.)"

    Wouldn't a C++11 initializer_list work here? Incidentally, I think C++11 initializer_list's are far superior to C99 compound literals; no nasty cast necessary.

    [Given that the question was asked before C++11 was ratified (or even had a name!), the suggestion is a bit moot. "Hey, this would be a lot easier if you used this technology that hasn't been invented yet." (Even today, you'll probably find that a lot of projects are still being built with pre-C++11 compilers.) -Raymond]

Comments are closed.