Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Share .cpp and .h along projects

12 views
Skip to first unread message

Stefano

unread,
Aug 9, 2007, 10:02:19 AM8/9/07
to
Hi,
I have written several general purpose class that I'd like to share
along my projects.
I'm using Visual C++ 6.0 and I've added the path of those files in
Directory \ Include Files, Directory \ Source Files of the IDE.
Everything works fine if the class is all coded inside the header (in
this case I add <MyClass.h>) but, if I need both .h and .cpp, I don't
know how to tell my project how to get the .cpp. The only way I found
was adding the source file to the project. Is there a better way ?

Thanks in advance,
Stefano

Robert

unread,
Aug 9, 2007, 10:40:00 AM8/9/07
to

Create a separate project as a static library or DLL. Add all reusable code
to this project (.h and .cpp). Then #include the ".h" file from other
(client) projects that need it and link to this library. Make sure you set
the reusable library as a project dependenciy in your client app. I suggeset
you create two such libraries in general. One should contain purely generic
code that has nothing to do with your app in particular (i.e., it can be
used by any arbitrary client - you can call it "Generic.lib" or
"Generic.Dll" for instance). The other should contain reusable code that's
specific to your application. If your app consists of multiple projects for
instance, you can create "Common.lib" or "Common.dll" and all projects in
your app can rely on it. These are general guidelines only of couse but they
work very well in practice.


Ulrich Eckhardt

unread,
Aug 10, 2007, 3:01:23 AM8/10/07
to
Stefano wrote:
> I have written several general purpose class that I'd like to share
> along my projects.
[...]

> Everything works fine if the class is all coded inside the header (in
> this case I add <MyClass.h>) but, if I need both .h and .cpp, I don't
> know how to tell my project how to get the .cpp. The only way I found
> was adding the source file to the project. Is there a better way ?

As mentioned, creating a proper library (static or dynamic) is a way.
Another way is to #include the .cpp file into exactly one translation unit
of your program.

Uli


Ben Voigt [C++ MVP]

unread,
Aug 13, 2007, 9:42:54 AM8/13/07
to

"Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
news:43fto4-...@satorlaser.homedns.org...

> Stefano wrote:
>> I have written several general purpose class that I'd like to share
>> along my projects.
> [...]
>> Everything works fine if the class is all coded inside the header (in
>> this case I add <MyClass.h>) but, if I need both .h and .cpp, I don't
>> know how to tell my project how to get the .cpp. The only way I found
>> was adding the source file to the project. Is there a better way ?
>
> As mentioned, creating a proper library (static or dynamic) is a way.

Blech. Please don't suggest exporting classes from Windows dynamic
libraries.

Ulrich Eckhardt

unread,
Aug 13, 2007, 10:28:13 AM8/13/07
to
Ben Voigt [C++ MVP] wrote:
> Blech. Please don't suggest exporting classes from Windows dynamic
> libraries.

Works for me and many others. Requires care. What exactly is your point?

Uli

Ben Voigt [C++ MVP]

unread,
Aug 13, 2007, 1:26:43 PM8/13/07
to

"Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
news:uc66p4-...@satorlaser.homedns.org...

It is incredibly fragile, non-standard behavior, that produces postings in
this newsgroup continually. The same behavior can be gotten in a robust,
compiler-independent way using COM-compatible binary interfaces. So why use
__declspec(dllexport)?


Doug Harrison [MVP]

unread,
Aug 13, 2007, 1:55:22 PM8/13/07
to
On Mon, 13 Aug 2007 12:26:43 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>It is incredibly fragile, non-standard behavior, that produces postings in
>this newsgroup continually. The same behavior can be gotten in a robust,
>compiler-independent way using COM-compatible binary interfaces. So why use
>__declspec(dllexport)?

Only __declspec(dllexport|dllimport) allows you to (mostly) use the classes
as you would write them in normal C++. Using the __declspec should be
viewed as equivalent to static linking, and then you won't lament the fact
it isn't compiler-independent; after all, it was never intended to be. In
comparison, COM is a huge pain in the neck, and I wouldn't even consider
using it unless there was an actual need to use it.

--
Doug Harrison
Visual C++ MVP

Alexander Nickolov

unread,
Aug 13, 2007, 3:12:51 PM8/13/07
to
COM is for binary compatibility. DLLs are binary objects
and it fits with them naturally. C++ classes are _not_ binary
entities and thus should _never_ be exported from DLLs -
period. It's a terrible deployment practice eclipising any bad
coding habits you might have! The proper way to export
C++ classes is using a static library. This signifies they are
part of your program, not part of a separate deployment unit.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://vcfaq.mvps.org
=====================================

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:ff61c3tt0otrfivah...@4ax.com...

Doug Harrison [MVP]

unread,
Aug 13, 2007, 3:50:02 PM8/13/07
to
On Mon, 13 Aug 2007 12:12:51 -0700, "Alexander Nickolov"
<agnic...@mvps.org> wrote:

>COM is for binary compatibility. DLLs are binary objects
>and it fits with them naturally. C++ classes are _not_ binary
>entities and thus should _never_ be exported from DLLs -
>period. It's a terrible deployment practice eclipising any bad
>coding habits you might have! The proper way to export
>C++ classes is using a static library. This signifies they are
>part of your program, not part of a separate deployment unit.

This use of DLLs is not what you're calling "deployment". If you consider
it to be a poor attempt at COM and not equivalent to static linking, well,
of course you're going to feel that way. That's what I was getting at in my
original message. It is mistaken expectations that cause people to say
things like this. You've mistaken it for something it's not, and anyone who
recognizes it for what it is and uses it correctly and successfully just
doesn't know what they're doing.

Larry Smith

unread,
Aug 13, 2007, 4:31:59 PM8/13/07
to
> COM is for binary compatibility. DLLs are binary objects
> and it fits with them naturally. C++ classes are _not_ binary
> entities and thus should _never_ be exported from DLLs -
> period. It's a terrible deployment practice eclipising any bad
> coding habits you might have! The proper way to export
> C++ classes is using a static library. This signifies they are
> part of your program, not part of a separate deployment unit.

That's your own personal (academic) opinion which shouldn't be preached as
if it were an inviolable statement of fact (far from it in my view).
Moreover, it's a fallacious argument to suggest that DLLs are "binary
objects" and therefore natural distribution units for objects that implement
a binary protocol like COM (exclusively over non-binary objects like C++
classes for instance). DLLs are just libraries and can expose anything
they're allowed to. COM components have no special status here (IMO).


Ben Voigt [C++ MVP]

unread,
Aug 13, 2007, 4:57:26 PM8/13/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:ff61c3tt0otrfivah...@4ax.com...
> On Mon, 13 Aug 2007 12:26:43 -0500, "Ben Voigt [C++ MVP]"
> <r...@nospam.nospam> wrote:
>
>>It is incredibly fragile, non-standard behavior, that produces postings in
>>this newsgroup continually. The same behavior can be gotten in a robust,
>>compiler-independent way using COM-compatible binary interfaces. So why
>>use
>>__declspec(dllexport)?
>
> Only __declspec(dllexport|dllimport) allows you to (mostly) use the
> classes
> as you would write them in normal C++. Using the __declspec should be

You can use COM-compatible v-table interfaces perfectly naturally... you
need a factory function and free function or reference counting instead of
new/delete, but that's all you lose. Add a smart pointer and you're about
as "normal C++" as you can get.

Ben Voigt [C++ MVP]

unread,
Aug 13, 2007, 5:03:13 PM8/13/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:11d1c39ijnaiema57...@4ax.com...

An updated DLL is expected to be a drag+drop replacement for the old
version.

__declspec(dllexport) is going to break that all the time, in a way that a
COM-compatible interface (doesn't have to be COM-compliant with all the
extra registry stuff) won't.

If you have such close coupling, and are using DLLs for incremental loading
or some other purpose besides deployment, then make sure every update to the
DLL changes the filename so nobody confuses it for being compatible with the
previous version.

And deploying "reusable components" that way is a huge mistake, which seems
to be what it gets used for 99% of the time, causing the constant questions
in this group of "how can I use this library XYZ I bought from vendor ABC
two years ago with the new Visual Studio?"

Generally, if something is so closely coupled that using a pure virtual
interface isn't good enough, you need C++ class definitions, then it needs
to be statically linked. Remember that giving the compiler access to the
class definition causes it to start inlining... what's the purpose of
putting the class in a separate DLL if the implementation is partially
inlined into the caller? You might as well put only half of the member
definitions in this DLL, a third in a second DLL, and a few in yet another
DLL.


Doug Harrison [MVP]

unread,
Aug 13, 2007, 6:30:27 PM8/13/07
to
On Mon, 13 Aug 2007 16:03:13 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>An updated DLL is expected to be a drag+drop replacement for the old
>version.

If a program is distributed as a.exe/a.dll, I would never assume I could
use a later version of a.dll with an older version of a.exe without being
told explicitly that this is OK.

>__declspec(dllexport) is going to break that all the time, in a way that a
>COM-compatible interface (doesn't have to be COM-compliant with all the
>extra registry stuff) won't.

You perceive that problem only because of your beliefs concerning DLL
substitutability. If you believed that __declspec(dllexport|dllimport) of
classes is equivalent to static linking WRT compilation dependencies, you
wouldn't think you could just overwrite a DLL with a newer version.
Instead, you'd understand you need to recompile all the DLL's clients.

>If you have such close coupling, and are using DLLs for incremental loading
>or some other purpose besides deployment, then make sure every update to the
>DLL changes the filename so nobody confuses it for being compatible with the
>previous version.

That's advisable for breaking changes when there are multiple clients that
cannot all be updated along with the DLL. (Example: The CRT and MFC DLLs.
Counter-example: DLLs developed for use by a program or group of programs
that are packaged as a whole.)

>And deploying "reusable components" that way is a huge mistake, which seems
>to be what it gets used for 99% of the time, causing the constant questions
>in this group of "how can I use this library XYZ I bought from vendor ABC
>two years ago with the new Visual Studio?"

I guess I've missed most of those questions. The ones I remember have to do
with CRT DLL mismatches and other genuine, technical problems affecting the
intersection of the C++ execution model with the Windows dynamic linking
model, which VC++ still needs to work on.

>Generally, if something is so closely coupled that using a pure virtual
>interface isn't good enough, you need C++ class definitions, then it needs
>to be statically linked. Remember that giving the compiler access to the
>class definition causes it to start inlining... what's the purpose of
>putting the class in a separate DLL if the implementation is partially
>inlined into the caller? You might as well put only half of the member
>definitions in this DLL, a third in a second DLL, and a few in yet another
>DLL.

Everything you said applies at least equally to static libraries, and since
I'm arguing that using __declspec(dllexport|dllimport) on classes must be
considered equivalent to static linking, a more interesting question would
be, "What are the advantages and disadvantages of using DLLs when static
linking is called for?"

Ulrich Eckhardt

unread,
Aug 14, 2007, 3:13:44 AM8/14/07
to
Ben Voigt [C++ MVP] wrote:
> "Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
> news:uc66p4-...@satorlaser.homedns.org...
>> Ben Voigt [C++ MVP] wrote:
>>> Blech. Please don't suggest exporting classes from Windows dynamic
>>> libraries.
>>
>> Works for me and many others. Requires care. What exactly is your point?
>
> It is incredibly fragile,

C++ is a sharp tool, right.

> non-standard behavior,

Yes, the C++ standard doesn't even describe thread. Would you advocate not
using them for that reason?

> that produces postings in this newsgroup continually.

Right, bad practice, documentation and tools are the reason why again and
again people have problems with this.

> The same behavior can be gotten in a robust, compiler-independent way
> using COM-compatible binary interfaces. So why use __declspec(dllexport)?

I haven't actually used COM sufficiently to make any statements about it.

BTW: C++ is not only about classes, it seems this has been assumed in this
thread somehow.

Uli

Ulrich Eckhardt

unread,
Aug 14, 2007, 3:20:27 AM8/14/07
to
Ben Voigt [C++ MVP] wrote:
> "Doug Harrison [MVP]" <d...@mvps.org> wrote in message
> news:ff61c3tt0otrfivah...@4ax.com...
>> Only __declspec(dllexport|dllimport) allows you to (mostly) use the
>> classes as you would write them in normal C++. [...]

>
> You can use COM-compatible v-table interfaces perfectly naturally... you
> need a factory function and free function or reference counting instead of
> new/delete, but that's all you lose. Add a smart pointer and you're about
> as "normal C++" as you can get.

Hmm, imagine that MFC's CPoint was exported via COM. The overhead would be
insane, every member access going through a function call, every
instantiation requiring a heap allocation.

Uli
(who is successfully deploying DLLs to a variety of platforms and with
several DLL versions and compiler ABIs in parallel)

Alex Blekhman

unread,
Aug 14, 2007, 6:17:48 AM8/14/07
to
Doug Harrison [MVP] wrote:
>> __declspec(dllexport) is going to break that all the time, in a way that a
>> COM-compatible interface (doesn't have to be COM-compliant with all the
>> extra registry stuff) won't.
>
> You perceive that problem only because of your beliefs concerning DLL
> substitutability. If you believed that __declspec(dllexport|dllimport) of
> classes is equivalent to static linking WRT compilation dependencies, you
> wouldn't think you could just overwrite a DLL with a newer version.
> Instead, you'd understand you need to recompile all the DLL's clients.

Actually, Ben has a point here. DLL's have long been boasted
of their substitutability and "after-market support", as
MSDN puts it. Without these advantages over static libs (who
cares about memory or disk savings anymore) DLL's have
nothing to offer.

Alex

Ben Voigt [C++ MVP]

unread,
Aug 14, 2007, 9:35:37 AM8/14/07
to

"Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
news:tm18p4-...@satorlaser.homedns.org...

> Ben Voigt [C++ MVP] wrote:
>> "Doug Harrison [MVP]" <d...@mvps.org> wrote in message
>> news:ff61c3tt0otrfivah...@4ax.com...
>>> Only __declspec(dllexport|dllimport) allows you to (mostly) use the
>>> classes as you would write them in normal C++. [...]
>>
>> You can use COM-compatible v-table interfaces perfectly naturally... you
>> need a factory function and free function or reference counting instead
>> of
>> new/delete, but that's all you lose. Add a smart pointer and you're
>> about
>> as "normal C++" as you can get.
>
> Hmm, imagine that MFC's CPoint was exported via COM. The overhead would be
> insane, every member access going through a function call, every
> instantiation requiring a heap allocation.

There's no code there worth sharing in a DLL. Share as source, derive
publicly from POINT and provide an implicit converting constructor, and let
all your APIs use POINT. Compiler independence achieved.


Ben Voigt [C++ MVP]

unread,
Aug 14, 2007, 9:47:44 AM8/14/07
to

"Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
news:aa18p4-...@satorlaser.homedns.org...

> Ben Voigt [C++ MVP] wrote:
>> "Ulrich Eckhardt" <eckh...@satorlaser.com> wrote in message
>> news:uc66p4-...@satorlaser.homedns.org...
>>> Ben Voigt [C++ MVP] wrote:
>>>> Blech. Please don't suggest exporting classes from Windows dynamic
>>>> libraries.
>>>
>>> Works for me and many others. Requires care. What exactly is your point?
>>
>> It is incredibly fragile,
>
> C++ is a sharp tool, right.

Anything C++ can do, can be done in a way that doesn't break when you change
compiler settings. When "good code" breaks due to a change in settings, we
call that an optimizer bug. We generally distinguish because an optimizer
bug only applies to particular versions of a single vendor's compiler, while
bad code is fragile with most or all compilers.

>
>> non-standard behavior,
>
> Yes, the C++ standard doesn't even describe thread. Would you advocate not
> using them for that reason?

It doesn't describe "printf" either.

Of course threads are standard, in IEEE POSIX 1003.1c-1995. Every major OS
(for general purpose computers) on the planet supports POSIX.


Doug Harrison [MVP]

unread,
Aug 14, 2007, 11:39:06 AM8/14/07
to
On Tue, 14 Aug 2007 13:17:48 +0300, Alex Blekhman <tkfx....@yahoo.com>
wrote:

>Actually, Ben has a point here. DLL's have long been boasted
>of their substitutability and "after-market support", as
>MSDN puts it. Without these advantages over static libs (who
>cares about memory or disk savings anymore) DLL's have
>nothing to offer.

Like I said, "If a program is distributed as a.exe/a.dll, I would never


assume I could use a later version of a.dll with an older version of a.exe

without being told explicitly that this is OK." I can't help it if some
think differently. They're simply wrong. When I hear stuff like this, it
helps me understand why MS locked Vista down in all the ways it did, but
then I can't fathom why anyone would replace one DLL with another on their
own initiative and expect everything to be fine without the developer of
the DLL telling them it's OK. I personally can remember doing this with two
versions of RICHED32.DLL, but that was just an experiment to help me
clarify a bug in one or the other so I could report it to MS. I never
expected it to be a viable solution to any problem I was having.

You may not care about memory/disk/download time/execution time savings,
and that's your right, but I hope you respect the fact that others may
still care about these things.

As for DLLs having "nothing to offer", consider:

1. As long as you are very careful, it is possible to fix bugs in libraries
distributed as DLLs, tell people it's OK to replace the old a.dll with the
new one, e.g. by publishing it on your web site, or if you're MS, on
Windows Update, and fix every EXE and DLL that uses a.dll in the process.

2. They offer finer (at least easier) control of visibility than static
libraries.

3. While static data initialization suffers from the DllMain restrictions,
the order of it is deterministic WRT different modules ("modules" being
other DLLs and EXE). Not so for static libraries (though you can mitigate
it with #pragma, albeit less reliably). This is very useful when one
library uses another, and there are global mutexes and the like that have
to be initialized.

4. Linking to DLLs may be faster than linking to static libs.

5. VC++ doesn't look into DLLs when performing interprocedural
optimization, which can be very useful to suppress in some contexts. Off
the top of my head, it eliminates one objection to the DCLP, and it takes
compiler optimizations out of the picture for mutex locking/unlocking.

6. IMO, it would be nuts to statically link numerous large libraries into a
program if there are other clients that could benefit from the size
reduction and points 1-5.

Alexander Nickolov

unread,
Aug 14, 2007, 1:56:09 PM8/14/07
to
It feels really weird to be called an academic for disseminating
an industry best practice...

Note I never said you _can't_ violate best practices, only that
it's not a good idea and you shouldn't do it.

I got kind of lost in your argument as well. The one thing I got
clear is that a DLL can expose whatever a DLL can expose.
Again, I don't dispute that. I duspute the need to advocate
bad practices. It's the same argument as for the C and C++
lagnguages themselves. They are powerful languages and give
you a lot of control, so you can easily shoot your proverbial
foot. Same with DLLs to a lesser extent. Best practices limit
the power of the tool to bring you safety, which you may not
even realize you need.

Ah, and I didn't ever say COM is the only use for DLLs.
The other best practice is using C level API with DLLs.
It's the older of the two, and still viable.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnic...@mvps.org
MVP VC FAQ: http://vcfaq.mvps.org
=====================================

"Larry Smith" <no_spam@_nospam.com> wrote in message
news:OJZ4Bke3...@TK2MSFTNGP02.phx.gbl...

Ben Voigt [C++ MVP]

unread,
Aug 14, 2007, 2:36:14 PM8/14/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:bvg3c35pu0790e3be...@4ax.com...

None of the above hold when using dllexport'ed classes, except possibly #3.
However, I presume that you mean global, not static data intialization.
Initialization of function static variables is well-defined.

#5 at least is a sign of code with real bugs in it.


Larry Smith

unread,
Aug 14, 2007, 3:24:16 PM8/14/07
to
> It feels really weird to be called an academic for disseminating
> an industry best practice

By "academic" I mean it's a theoretical argument that's really just an
(educated) opinion. I can't say I recall any MSFT literature that mentions
this issue as a "best practice" however. Even if they claim it is somewhere
(I'd be interested in seeing it), it's just a guideline that educated
programmers can choose to ignore. It's a matter of judgment after all and it
was MSFT who provided this capability in the first place.

> Note I never said you _can't_ violate best practices, only that
> it's not a good idea and you shouldn't do it.

Terms like "never", "terrible", "proper", etc. are very strong words that
indicate it's an absoloute no-no that you ever do this. That's just your own
opinion however which you obviously consider prudent. One can respect that
and I do. However, it's not factually correct in my own opinion because you
can do it and safely. Your comments imply the opposite even if all you meant
is that it can be risky and should therefore be avoided. Moreover, a
statement so bold as to suggest it's worse than any bad coding habits one
might have is completely misguided when bad coding habits are far more
egregious than this issue (the very reason most software in the real world
is of such poor quality)

> I got kind of lost in your argument as well. The one thing I got
> clear is that a DLL can expose whatever a DLL can expose.
> Again, I don't dispute that. I duspute the need to advocate
> bad practices. It's the same argument as for the C and C++
> lagnguages themselves. They are powerful languages and give
> you a lot of control, so you can easily shoot your proverbial
> foot. Same with DLLs to a lesser extent. Best practices limit
> the power of the tool to bring you safety, which you may not
> even realize you need.

My point is that DLLs allow you to expose whatever they allow which includes
C++ classes. It's perfectly safe and legal if you know what you're doing.
The (romantic) idea that DLLs are binary objects and shouldn't be used for
this, or that they're somehow more sutitable for COM objects because of this
loose definition of the term "binary object", well, it's a real stretch for
me.

> Ah, and I didn't ever say COM is the only use for DLLs.

I didn't mean to imply that you did. I also wasn't questionng your judgment
or experience which is extensive no doubt. I just don't think it's correct
to pass off advice as if it were absolute.


Ben Voigt [C++ MVP]

unread,
Aug 14, 2007, 5:14:11 PM8/14/07
to
> My point is that DLLs allow you to expose whatever they allow which
> includes C++ classes. It's perfectly safe and legal if you know what
> you're doing. The (romantic) idea that DLLs are binary objects and
> shouldn't be used for this, or that they're somehow more sutitable for COM
> objects because of this loose definition of the term "binary object",
> well, it's a real stretch for me.

No, DLLs expose functions and (very occasionally) global data.

The fact that the C++ compiler flattens the hierarchy of C++ class members
using name mangling, in such a way that they can be forced into the DLL
export table, doesn't mean that the DLL is truly exporting classes. For
what's necessary to *truly* export a C++ class, see
(http://www.comeaucomputing.com/4.0/docs/userman/export.html). DLLs do not
provide that. They do not even come close.

Among other things, C++ classes must abide by the ODR rule. DLLs provide no
help avoiding ODR violations.


Doug Harrison [MVP]

unread,
Aug 14, 2007, 5:34:39 PM8/14/07
to

I have direct experience that (1), (3), and (5) hold for classes, and IMO
(6) is generally true. For (5), you have to be careful not to define any
inline functions or let the compiler define any default functions for you.

>However, I presume that you mean global, not static data intialization.

Yeah, a good clue to that is what I later said about "global mutexes".

>Initialization of function static variables is well-defined.

Ironically, it's the order of destruction of local statics that can be a
problem with DLLs. That's one of the things I was alluding to when I said
earlier, "... technical problems affecting the intersection of the C++


execution model with the Windows dynamic linking model, which VC++ still
needs to work on."

>#5 at least is a sign of code with real bugs in it.

Oh, please. As I mentioned, being able to suppress interprocedural
optimization is a necessity for implementing mutex lock/unlock operations,
and it also eliminates one of the problems in the DCLP. By putting
WaitForSingleObject, ReleaseMutex, and others in opaque system DLLs,
correct compiler behavior for MT programming WRT these operations
essentially comes for free.

Larry Smith

unread,
Aug 14, 2007, 7:19:48 PM8/14/07
to
> No, DLLs expose functions and (very occasionally) global data.
>
> The fact that the C++ compiler flattens the hierarchy of C++ class members
> using name mangling, in such a way that they can be forced into the DLL
> export table, doesn't mean that the DLL is truly exporting classes. For
> what's necessary to *truly* export a C++ class, see
> (http://www.comeaucomputing.com/4.0/docs/userman/export.html). DLLs do
> not provide that. They do not even come close.

I never claimed this is equivalent to the "export" keyword which wasn't even
implemented outside of Comeau the last time I checked (quite sometime ago).
This issue has nothing to do with the "export" keyword however which is a
template mechanism. Note BTW that "export" has serious issues surrounding it
and Herb Sutter has even recommended removing it from the standard
altogether (in a paper read some years back).

> Among other things, C++ classes must abide by the ODR rule. DLLs provide
> no help avoiding ODR violations.

"dllexport" is a documented MSFT extension that allows you to "export"
classes. It's that simple. Follow the rules and you'll be ok. All these
negative claims that you should never do this and never do that is nonsense.
I last used this feature some years ago and there were never any problems.
No ODR problems, no shared CRT library problems (when dealing with memory
across DLL boundaries), etc. Compiler-independence, the use of COM, and
other issues I've read in this thread are irrelevant to those who don't need
or want it. Someone also mentioned the difficutlites of using COM as a
deterrent. It is, and a very serious one since COM has a very steep learning
curve. Moreover, the C++ tools for creating COM objects never matured
properly. #import and the associated smart pointers was a nice creation for
COM clients, but for most COM server writers, ATL or even MFC is a disaster
waiting to happen (for most programmers).


Ben Voigt [C++ MVP]

unread,
Aug 15, 2007, 9:32:27 AM8/15/07
to
>>#5 at least is a sign of code with real bugs in it.
>
> Oh, please. As I mentioned, being able to suppress interprocedural
> optimization is a necessity for implementing mutex lock/unlock operations,

You use the volatile keyword for that, then you are robust against future
improvements in the optimizer.

> and it also eliminates one of the problems in the DCLP. By putting
> WaitForSingleObject, ReleaseMutex, and others in opaque system DLLs,
> correct compiler behavior for MT programming WRT these operations
> essentially comes for free.

Again, you use the volatile keyword for variables that are volatile. Trying
to get volatile behavior by changing to DLLs is bad.


Ben Voigt [C++ MVP]

unread,
Aug 15, 2007, 9:37:38 AM8/15/07
to

"Larry Smith" <no_spam@_nospam.com> wrote in message
news:OWwVdms3...@TK2MSFTNGP04.phx.gbl...

>> No, DLLs expose functions and (very occasionally) global data.
>>
>> The fact that the C++ compiler flattens the hierarchy of C++ class
>> members using name mangling, in such a way that they can be forced into
>> the DLL export table, doesn't mean that the DLL is truly exporting
>> classes. For what's necessary to *truly* export a C++ class, see
>> (http://www.comeaucomputing.com/4.0/docs/userman/export.html). DLLs do
>> not provide that. They do not even come close.
>
> I never claimed this is equivalent to the "export" keyword which wasn't
> even implemented outside of Comeau the last time I checked (quite sometime
> ago). This issue has nothing to do with the "export" keyword however which
> is a template mechanism. Note BTW that "export" has serious issues
> surrounding it and Herb Sutter has even recommended removing it from the
> standard altogether (in a paper read some years back).

Templates weren't what you were referring to when you said "C++ is more than
just classes"?

>
>> Among other things, C++ classes must abide by the ODR rule. DLLs provide
>> no help avoiding ODR violations.
>
> "dllexport" is a documented MSFT extension that allows you to "export"
> classes. It's that simple. Follow the rules and you'll be ok. All these
> negative claims that you should never do this and never do that is
> nonsense. I last used this feature some years ago and there were never any
> problems. No ODR problems, no shared CRT library problems (when dealing
> with memory across DLL boundaries), etc. Compiler-independence, the use of
> COM, and other issues I've read in this thread are irrelevant to those who
> don't need or want it. Someone also mentioned the difficutlites of using
> COM as a deterrent. It is, and a very serious one since COM has a very
> steep learning curve. Moreover, the C++ tools for creating COM objects
> never matured properly. #import and the associated smart pointers was a
> nice creation for COM clients, but for most COM server writers, ATL or
> even MFC is a disaster waiting to happen (for most programmers).

COM is not necessary. Using v-tables the same way COM does, is. You don't
have to mess with apartments or registration or marshalling or remoting
contexts or pumping the message loop or any of that to export objects from
DLLs in a compiler-independent way.

The C++ tools for creating v-tables are quite mature in my experience. You
just use the "virtual" keyword and the "= 0" pure modifier, and create
helper "factory" functions to create and destroy the objects, which is
necessary anyway because objects have to be freed in the same module they
are created from to avoid heap corruption.


Doug Harrison [MVP]

unread,
Aug 15, 2007, 9:14:36 PM8/15/07
to

Uh, no.

The "volatile" keyword has essentially no use in multithreaded programming,
although MS defining it to have memory barrier semantics in VC2005 makes
its misuse more likely to work on systems with weakly ordered memory
systems. It also provides expert users with a tool they can use
non-portably, for those occasional times when it provides something of
value.

The mutex lock/unlock situation I've been talking about is illustrated by
this:

mutex mx;
int x;

// None of these touch x.
void lock(mutex&);
void unlock(mutex&);
void g(int);

void f1()
{
lock(mx);
g(x);
unlock(mx);

lock(mx);
g(x);
unlock(mx);
}

void f2()
{
lock(mx);
++x;
unlock(mx);
}

A compiler that can see into all these functions will observe that none of
lock, unlock, and g access x, so it can cache the value of x across the
mutex calls in f1. This of course is incorrect for multithreaded
programming. In addition, the compiler could potentially move the initial
read of x in front of f's first critical section. Putting lock/unlock into
an opaque DLL suppresses this optimization, because the compiler must
assume that all globals are reachable from functions it can't see into. If
the compiler could look into the DLL, there would have to be some way to
explicitly indicate that lock/unlock are unsafe for this optimization.

The DCLP issue is similar, in that placing the ctor of the created object
in an opaque DLL prevents the compiler from updating the local static
pointer before the ctor has finished.

Larry Smith

unread,
Aug 15, 2007, 9:43:17 PM8/15/07
to
>> I never claimed this is equivalent to the "export" keyword which wasn't
>> even implemented outside of Comeau the last time I checked (quite
>> sometime ago). This issue has nothing to do with the "export" keyword
>> however which is a template mechanism. Note BTW that "export" has serious
>> issues surrounding it and Herb Sutter has even recommended removing it
>> from the standard altogether (in a paper read some years back).
>
> Templates weren't what you were referring to when you said "C++ is more
> than just classes"?

I never said that nor alluded to it (intentionally anyway)

> The C++ tools for creating v-tables are quite mature in my experience.

Tools don't create vtables normally. The compiler does. Nevertheless, the
tools I referred to are those used to create COM objects in the first place.
ATL is really the de facto way in C++ but ATL *is* immature IMO. Yes it
works but it's arcane and extremely difficult to master to say the least.
Since you're not personally advocating the use of COM however (in this
discussion) then it's immaterial here..

> You just use the "virtual" keyword and the "= 0" pure modifier, and create
> helper "factory" functions to create and destroy the objects, which is
> necessary anyway because objects have to be freed in the same module they
> are created from to avoid heap corruption.

That only affects memory and other resources allocated with one copy of the
CRT and freed with another. If you share the same CRT then there's no
problem. If you create your object on the stack then there's no problem
(though internal resources allocated by the class obviously have to respect
the rules as well). This is all irrelevant anyway. You have to play by the
rules and nobody disputes that. In fact, I would never personally expose raw
resources this way for the very reason you mentioned. You allow the DLL
itself to allocate and free all resources, possibly by providing appropriate
client methods for this purpose (which is what you're basically advocating
by using factory functions). While other approaches and strategies obviously
exist for creating reusable "components" (however one defines that term), my
beef is with those who claim that C++ classes should never be exposed from a
DLL. That's completely absurd. Not only is it legal, but it was MSFT who
expliciitly provided this as language extension in the first place. If a
programmer who (really) knows what he's doing feels this is the best way to
accomplish his task, then it's perfectly acceptable.


Ben Voigt [C++ MVP]

unread,
Aug 16, 2007, 9:41:35 AM8/16/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:qm87c3li5ota3g5bi...@4ax.com...

That is exactly what "volatile" does. Even in old versions of the compiler,
when memory barriers are not used, volatile exactly prevents the
optimization you are speaking of, in a way that is not fragile to moving
source files between DLLs, changing optimization settings, etc.

Provided that "lock" and "unlock" trigger memory barriers, the code you show
will be correct on any version of the C++ compiler (even non-Microsoft
compilers), using any optimization settings, if you would only say "volatile
int x".

In fact, if your "x" is in a anonymous namespace, which would be the
recommended practice, and you do not take its address, then the compiler
aliasing analysis can determine that it will not be touched by the "opaque"
DLL, and reorder or remove the access to x once again.

You *MUST* use "volatile" in this case to guarantee correct behavior, and
when you do, whether the lock and unlock functions are in a DLL is no longer
important.

>
> The DCLP issue is similar, in that placing the ctor of the created object
> in an opaque DLL prevents the compiler from updating the local static
> pointer before the ctor has finished.

No. Unless you have a memory barrier, other threads could see the change to
the pointer before the change to the members of the object are visible.
Your games with DLLs will not save you. "volatile" will save you in VC2005,
and "volatile" combined with InterlockedExchangePointer to update the
pointer is the correct, portable way of doing so.


Doug Harrison [MVP]

unread,
Aug 16, 2007, 1:34:22 PM8/16/07
to
On Thu, 16 Aug 2007 08:41:35 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>That is exactly what "volatile" does. Even in old versions of the compiler,
>when memory barriers are not used, volatile exactly prevents the
>optimization you are speaking of, in a way that is not fragile to moving
>source files between DLLs, changing optimization settings, etc.
>
>Provided that "lock" and "unlock" trigger memory barriers, the code you show
>will be correct on any version of the C++ compiler (even non-Microsoft
>compilers), using any optimization settings, if you would only say "volatile
>int x".

You can't require people to use volatile on top of synchronization.
Synchronization needs to be sufficient all by itself, and it is in any
compiler useful for multithreaded programming.

1. You're not thinking this through. Make x a vector<int>, and then what?
Cast volatile away so you can call its member functions? More generally,
how do you take existing code and use it in a thread-safe way?

2. Using volatile can be a huge performance killer, because it suppresses
optimization inside critical sections, where it's perfectly fine to use.

3. Reading and writing a volatile variable is ordered only WRT reading and
writing other volatile variables and other observable behavior, so what
you're proposing requires pretty much everything to be volatile.

All you need is synchronization.

>In fact, if your "x" is in a anonymous namespace, which would be the
>recommended practice, and you do not take its address, then the compiler
>aliasing analysis can determine that it will not be touched by the "opaque"
>DLL, and reorder or remove the access to x once again.

That optimization is not safe for a compiler useful for multithreaded
programming. Note that I didn't say "touched" by the DLL; I said
"reachable" from the DLL. The difference is, the compiler would have to
prove no potential code path in a MT program accesses the variable, which
is quite a tall order.

>You *MUST* use "volatile" in this case to guarantee correct behavior, and
>when you do, whether the lock and unlock functions are in a DLL is no longer
>important.

You can't require people to use volatile on top of synchronization.
Synchronization needs to be sufficient all by itself, and it is in any
compiler useful for multithreaded programming. All you need is
synchronization.

BTW, whether or not lock/unlock are in a DLL is incidental. The important
thing is for the compiler not to perform unsafe optimizations around them,
and this could be specified by tagging them with a __declspec.

>No. Unless you have a memory barrier, other threads could see the change to
>the pointer before the change to the members of the object are visible.

I said, "it eliminates one of the problems in the DCLP", the key word being
"one". Memory barriers address another issue. The problem I described is
the one James Kanze brought to my attention in this discussion we were
having back in 1999, specifically his concern about inlining the ctor:

http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/13603168924676f5/956334d3a6e92e11?#956334d3a6e92e11

Looking at it now, he may have been overly pessimistic, since the compiler
may inhibit optimizing around the memory barrier instructions I used in my
example.

>Your games with DLLs will not save you. "volatile" will save you in VC2005,
>and "volatile" combined with InterlockedExchangePointer to update the
>pointer is the correct, portable way of doing so.

Clearly, you think volatile is far more relevant than it is. You should
google this group as well as comp.programming.threads for past discussions
on "volatile".

Ben Voigt [C++ MVP]

unread,
Aug 16, 2007, 5:30:28 PM8/16/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:p7r8c399sc57e5k24...@4ax.com...

> On Thu, 16 Aug 2007 08:41:35 -0500, "Ben Voigt [C++ MVP]"
> <r...@nospam.nospam> wrote:
>
>>That is exactly what "volatile" does. Even in old versions of the
>>compiler,
>>when memory barriers are not used, volatile exactly prevents the
>>optimization you are speaking of, in a way that is not fragile to moving
>>source files between DLLs, changing optimization settings, etc.
>>
>>Provided that "lock" and "unlock" trigger memory barriers, the code you
>>show
>>will be correct on any version of the C++ compiler (even non-Microsoft
>>compilers), using any optimization settings, if you would only say
>>"volatile
>>int x".
>
> You can't require people to use volatile on top of synchronization.
> Synchronization needs to be sufficient all by itself, and it is in any
> compiler useful for multithreaded programming.
>
> 1. You're not thinking this through. Make x a vector<int>, and then what?
> Cast volatile away so you can call its member functions? More generally,
> how do you take existing code and use it in a thread-safe way?

Only single machine words can be used for interlocked operations, but any
larger object can be controlled in a threadsafe manner using a volatile
pointer (which is word-sized) and memory barriers.

>
> 2. Using volatile can be a huge performance killer, because it suppresses
> optimization inside critical sections, where it's perfectly fine to use.
>
> 3. Reading and writing a volatile variable is ordered only WRT reading and
> writing other volatile variables and other observable behavior, so what
> you're proposing requires pretty much everything to be volatile.

"other observable behavior" covers a lot of things.

>
> All you need is synchronization.

Synchronization is defined using volatile primitives.

>
>>In fact, if your "x" is in a anonymous namespace, which would be the
>>recommended practice, and you do not take its address, then the compiler
>>aliasing analysis can determine that it will not be touched by the
>>"opaque"
>>DLL, and reorder or remove the access to x once again.
>
> That optimization is not safe for a compiler useful for multithreaded
> programming. Note that I didn't say "touched" by the DLL; I said
> "reachable" from the DLL. The difference is, the compiler would have to
> prove no potential code path in a MT program accesses the variable, which
> is quite a tall order.

Compiler optimizations (and CPU reorders) are always defined in terms of
equivalence on a single sequence of execution.


Doug Harrison [MVP]

unread,
Aug 17, 2007, 12:46:17 PM8/17/07
to
On Thu, 16 Aug 2007 16:30:28 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>Only single machine words can be used for interlocked operations, but any
>larger object can be controlled in a threadsafe manner using a volatile
>pointer (which is word-sized) and memory barriers.

That's nonsense, but simplify it to a uniprocessor system, which does not
have memory barriers. Heck, you could even limit it to x86 SMP systems.
Explain how you can use (say) a std::vector in a thread-safe way by "using
a volatile pointer".

Note that people who actually understand how to do multithreaded
programming and use synchronization objects such as mutexes rarely if ever
use volatile and may not even have heard of memory barriers, which they
don't need to know about in order to use synchronization objects.

<snip>

>Compiler optimizations (and CPU reorders) are always defined in terms of
>equivalence on a single sequence of execution.

Some of those optimizations cannot be safely applied by a compiler intended
to be useful for multithreaded programming. For some reason, that point is
not getting across.

Ben Voigt [C++ MVP]

unread,
Aug 17, 2007, 4:14:50 PM8/17/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:o9ibc35kas1d7r9j0...@4ax.com...

> On Thu, 16 Aug 2007 16:30:28 -0500, "Ben Voigt [C++ MVP]"
> <r...@nospam.nospam> wrote:
>
>>Only single machine words can be used for interlocked operations, but any
>>larger object can be controlled in a threadsafe manner using a volatile
>>pointer (which is word-sized) and memory barriers.
>
> That's nonsense, but simplify it to a uniprocessor system, which does not
> have memory barriers. Heck, you could even limit it to x86 SMP systems.
> Explain how you can use (say) a std::vector in a thread-safe way by "using
> a volatile pointer".

volatile std::vector* g_sharedVector;

...

{
std::vector* pvector = NULL;

// this is a spin-wait, efficient on SMP, but on a single processor
system a yield should be inserted
while (pvector == NULL) {
pvector = InterlockedExchangePointerAcquire(&g_sharedVector, NULL);
}

// use pvector in any way desired

// with VC2005, can use "g_sharedVector = pvector;" instead
InterlockedExchangePointerRelease(&g_sharedVector, pvector);
}

>
> Note that people who actually understand how to do multithreaded
> programming and use synchronization objects such as mutexes rarely if ever
> use volatile and may not even have heard of memory barriers, which they
> don't need to know about in order to use synchronization objects.

They may not use volatile explicitly, but there aren't any
multiprocessor-capable synchronization objects that don't use volatile
interlocked primitives with memory barriers.

>
> <snip>
>
>>Compiler optimizations (and CPU reorders) are always defined in terms of
>>equivalence on a single sequence of execution.
>
> Some of those optimizations cannot be safely applied by a compiler
> intended
> to be useful for multithreaded programming. For some reason, that point is
> not getting across.

The compiler DOES apply those optimizations. If the code doesn't make
proper use of volatile and memory barriers to ensure that the correct data
is seen in other threads, then the code has a thread safety issue, not the
compiler.

Splitting functions into separate DLLs to prevent the optimizations is not
the right thing to do. It is fragile. For example, at one time simply
using two different compilation units within the same module would prevent
optimization. Now there is Link-Time Code Generation. Also, the .NET JIT
compiler does cross-assembly inlining and even native compilation can make
deductions and optimize across external calls using aliasing analysis.
Using "volatile" is the only way to make code robust in the face of
improving optimizing compilers, and as a bonus, it is part of the C++
standard.


Doug Harrison [MVP]

unread,
Aug 17, 2007, 8:47:36 PM8/17/07
to
On Fri, 17 Aug 2007 15:14:50 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>volatile std::vector* g_sharedVector;
>
>...
>
>{
> std::vector* pvector = NULL;
>
> // this is a spin-wait, efficient on SMP, but on a single processor
>system a yield should be inserted
> while (pvector == NULL) {
> pvector = InterlockedExchangePointerAcquire(&g_sharedVector, NULL);
> }
>
> // use pvector in any way desired

With this sort of busy-waiting, it's more important than ever that "any way
desired" translate to "as short a time as possible".

> // with VC2005, can use "g_sharedVector = pvector;" instead
> InterlockedExchangePointerRelease(&g_sharedVector, pvector);
>}

The sensible thing is to get rid of the pointers and use a CRITICAL_SECTION
along with the vector object instead of this clumsy, inefficient, obscure,
limited alternative to the way people have been doing things since the
beginning of Windows NT.

BTW, why _exactly_ did you use volatile in your declaration of
g_sharedVector? (Based on the declaration of
InterlockedExchangePointerAcquire, it shouldn't even compile.)

>The compiler DOES apply those optimizations. If the code doesn't make
>proper use of volatile and memory barriers to ensure that the correct data
>is seen in other threads, then the code has a thread safety issue, not the
>compiler.

I'll say it again:

<q>


You can't require people to use volatile on top of synchronization.
Synchronization needs to be sufficient all by itself, and it is in any

compiler useful for multithreaded programming. All you need is
synchronization.
</q>

As you've become fixated on memory barriers, I'll add that using
synchronization objects takes care of whatever memory barriers may be
needed. Most multithreaded programming is done in terms of mutexes and the
like, and thinking about memory barriers is not necessary when using
mutexes and the like.

>Splitting functions into separate DLLs to prevent the optimizations is not
>the right thing to do. It is fragile. For example, at one time simply
>using two different compilation units within the same module would prevent
>optimization. Now there is Link-Time Code Generation. Also, the .NET JIT
>compiler does cross-assembly inlining and even native compilation can make
>deductions and optimize across external calls using aliasing analysis.

As I've said a couple of times by now, "If the compiler could look into the


DLL, there would have to be some way to explicitly indicate that

lock/unlock are unsafe for this optimization." Do you understand I'm not
saying that the DLL approach is the be-all, end-all solution to the
problem? Do you understand what I meant when I said, "By putting


WaitForSingleObject, ReleaseMutex, and others in opaque system DLLs,
correct compiler behavior for MT programming WRT these operations

essentially comes for free." That means as long as the compiler doesn't
perform optimizations unsafe for multithreading around calls to these
functions, it does not need to define a way to mark their declarations
unsafe. It also means you don't have to use volatile, because the compiler
must assume these functions can affect observable behavior involving the
objects you want to needlessly declare volatile, which as I've already
noted, is a huge performance killer plus completely impractical to use for
class objects.

>Using "volatile" is the only way to make code robust in the face of
>improving optimizing compilers, and as a bonus, it is part of the C++
>standard.

That's really quite funny. The C++ Standard does not address
multithreading, and it was recognized long ago that volatile is not the
answer or even a significant part of the answer. You might begin to
understand these things I've been talking about if you'd take the advice I
gave you a couple of messages ago:

<q>


You should google this group as well as comp.programming.threads for past
discussions on "volatile".

</q>

If you won't do this but reply with more of the same, you will have the
last word.

Ben Voigt [C++ MVP]

unread,
Aug 20, 2007, 10:57:14 AM8/20/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:nefcc3hh5er85j8hd...@4ax.com...

> On Fri, 17 Aug 2007 15:14:50 -0500, "Ben Voigt [C++ MVP]"
> <r...@nospam.nospam> wrote:
>
>>volatile std::vector* g_sharedVector;
>>
>>...
>>
>>{
>> std::vector* pvector = NULL;
>>
>> // this is a spin-wait, efficient on SMP, but on a single processor
>>system a yield should be inserted
>> while (pvector == NULL) {
>> pvector = InterlockedExchangePointerAcquire(&g_sharedVector,
>> NULL);
>> }
>>
>> // use pvector in any way desired
>
> With this sort of busy-waiting, it's more important than ever that "any
> way
> desired" translate to "as short a time as possible".

Agreed. But that's true for any access to synchronized resources.

>
>> // with VC2005, can use "g_sharedVector = pvector;" instead
>> InterlockedExchangePointerRelease(&g_sharedVector, pvector);
>>}
>
> The sensible thing is to get rid of the pointers and use a
> CRITICAL_SECTION
> along with the vector object instead of this clumsy, inefficient, obscure,
> limited alternative to the way people have been doing things since the
> beginning of Windows NT.

That's a higher level of abstraction for doing exactly the same thing.

You keep making the same false claim. Here, let me show you a variant of
your code that is going to fail with an intelligent compiler, no peering
into the DLL necessary:

namespace {
// assume the address of x is never taken
int x; // needs to be volatile
mutex mx;
}

// None of these touch x.
void lock(mutex&);
void unlock(mutex&);
void g(int);

void f1()
{
lock(mx);
g(x);
unlock(mx);

lock(mx);
g(x);
unlock(mx);
}

void f2()
{
lock(mx);
++x;
unlock(mx);
}

As you stated "A compiler that can see into all these functions will observe

that none of lock, unlock, and g access x, so it can cache the value of x

across the mutex calls in f1." I tell you that because x has file-local
linkage and the address of x is not taken, aliasing analysis in current
compilers proves that none of lock, unlock, or g access x -- without seeing
into the functions.

Using DLLs to inhibit optimization is broken, Broken, BROKEN! Adding
"volatile" to the declaration of x fixes the problem.


> must assume these functions can affect observable behavior involving the
> objects you want to needlessly declare volatile, which as I've already
> noted, is a huge performance killer plus completely impractical to use for
> class objects.

It is *not* a performance killer when used correctly. Look at my original
example above and note that pvector is not declared volatile, only the
shared pointer is. Within the critical section all optimizations are
possible.

>
>>Using "volatile" is the only way to make code robust in the face of
>>improving optimizing compilers, and as a bonus, it is part of the C++
>>standard.
>
> That's really quite funny. The C++ Standard does not address
> multithreading, and it was recognized long ago that volatile is not the
> answer or even a significant part of the answer. You might begin to
> understand these things I've been talking about if you'd take the advice I
> gave you a couple of messages ago:
>
> <q>
> You should google this group as well as comp.programming.threads for past
> discussions on "volatile".
> </q>

I've read several of those threads, some of which are in the hundreds of
responses.

Doug Harrison [MVP]

unread,
Aug 20, 2007, 3:52:24 PM8/20/07
to
On Mon, 20 Aug 2007 09:57:14 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
>news:nefcc3hh5er85j8hd...@4ax.com...
>> On Fri, 17 Aug 2007 15:14:50 -0500, "Ben Voigt [C++ MVP]"
>> <r...@nospam.nospam> wrote:
>>
>>>volatile std::vector* g_sharedVector;
>>>
>>>...
>>>
>>>{
>>> std::vector* pvector = NULL;
>>>
>>> // this is a spin-wait, efficient on SMP, but on a single processor
>>>system a yield should be inserted
>>> while (pvector == NULL) {
>>> pvector = InterlockedExchangePointerAcquire(&g_sharedVector,
>>> NULL);
>>> }
>>>
>>> // use pvector in any way desired
>>
>> With this sort of busy-waiting, it's more important than ever that "any
>> way
>> desired" translate to "as short a time as possible".
>
>Agreed. But that's true for any access to synchronized resources.

It's especially true when you present a method that uses 100% of the CPU
for an indefinite period of time.

>>> // with VC2005, can use "g_sharedVector = pvector;" instead
>>> InterlockedExchangePointerRelease(&g_sharedVector, pvector);
>>>}
>>
>> The sensible thing is to get rid of the pointers and use a
>> CRITICAL_SECTION
>> along with the vector object instead of this clumsy, inefficient, obscure,
>> limited alternative to the way people have been doing things since the
>> beginning of Windows NT.
>
>That's a higher level of abstraction for doing exactly the same thing.

Common sense dictates using the highest-level abstraction available unless
there's a good, specific reason to do otherwise. If, when someone says
"mutex", as I repeatedly did, you think "InterlockedXXX", well, it's just
hard to understand why.

>> BTW, why _exactly_ did you use volatile in your declaration of
>> g_sharedVector? (Based on the declaration of
>> InterlockedExchangePointerAcquire, it shouldn't even compile.)

No answer? I really would like to hear what you think volatile accomplishes
here.

And I'll tell you again, you're not thinking this through. As I've
explained several times already, for a compiler useful for multithreading
to apply this optimization, it would have to prove there is no way x is
reachable from lock/unlock. This means proving there is no way f2 can be
called as a result of calling lock/unlock. The compiler cannot prove this
without being able to see into lock/unlock. This is the basis for what I've
said about opaque DLLs.

To a large extent, this is not even a multithreading issue. It also applies
to single-threaded code.

(My example does assume that f1 and f2 are called sometime, somewhere. If
one of them isn't, it's not very interesting.)

>Using DLLs to inhibit optimization is broken, Broken, BROKEN!

Then stop bellowing and (a) Demonstrate that it doesn't work, and (b) Show
why the compiler doesn't perform unsafe optimizations around
WFSO/ReleaseMutex, EnterCriticalSection/LeaveCriticalSection, etc.

>Adding "volatile" to the declaration of x fixes the problem.

Except that you cannot require people to use volatile on top of
synchronization.

>> must assume these functions can affect observable behavior involving the


>> objects you want to needlessly declare volatile, which as I've already
>> noted, is a huge performance killer plus completely impractical to use for
>> class objects.
>
>It is *not* a performance killer when used correctly. Look at my original
>example above and note that pvector is not declared volatile, only the
>shared pointer is. Within the critical section all optimizations are
>possible.

Before you make claims about your use of "volatile", answer the question I
posed last time:

>> BTW, why _exactly_ did you use volatile in your declaration of
>> g_sharedVector? (Based on the declaration of
>> InterlockedExchangePointerAcquire, it shouldn't even compile.)

This is an important question for you to answer in detail.

>>>Using "volatile" is the only way to make code robust in the face of
>>>improving optimizing compilers, and as a bonus, it is part of the C++
>>>standard.
>>
>> That's really quite funny. The C++ Standard does not address
>> multithreading, and it was recognized long ago that volatile is not the
>> answer or even a significant part of the answer. You might begin to
>> understand these things I've been talking about if you'd take the advice I
>> gave you a couple of messages ago:
>>
>> <q>
>> You should google this group as well as comp.programming.threads for past
>> discussions on "volatile".
>> </q>
>
>I've read several of those threads, some of which are in the hundreds of
>responses.

Gee, I wonder why they get to be so long? :) FWIW, I'm not the only MVP who
has said things like "volatile is neither necessary nor sufficient" for
multithreaded programming using synchronization objects like mutexes. Of
course, we're getting this from people like Butenhof, who played a big part
in the pthreads standard.

Part of the problem is that MS has yet to publish a formal set of memory
visibility rules like POSIX did years ago, or I would have pointed you to
that. This leaves me to argue from experience writing MT code in VC++ and
also the fact that it would be a colossal blunder not to follow the POSIX
rules, which specifically do not require volatile on top of
synchronization. Also, I cannot recall ever hearing of a bug resulting from
not using volatile on variables consistently accessed under the protection
of a locking protocol involving CRITICAL_SECTIONs, kernel mutexes, and
other Windows synchronization objects. I cannot recall any MS documentation
that says volatile must be used on top of synchronization. The MFC library
doesn't use volatile, nor does the CRT use volatile for things it protects
with CRITICAL_SECTIONs, such as FILE objects.

For all these reasons, I think I'm on pretty safe ground (to put it mildly)
when I say that volatile is not required when using synchronization. If you
still disagree, I ask you to produce a counter-example.

I think I'm on equally safe ground WRT what I've said about DLLs. If you
still disagree, I ask you to produce a counter-example.

Ben Voigt [C++ MVP]

unread,
Aug 20, 2007, 6:16:11 PM8/20/07
to
>>> The sensible thing is to get rid of the pointers and use a
>>> CRITICAL_SECTION
>>> along with the vector object instead of this clumsy, inefficient,
>>> obscure,
>>> limited alternative to the way people have been doing things since the
>>> beginning of Windows NT.
>>
>>That's a higher level of abstraction for doing exactly the same thing.
>
> Common sense dictates using the highest-level abstraction available unless
> there's a good, specific reason to do otherwise. If, when someone says
> "mutex", as I repeatedly did, you think "InterlockedXXX", well, it's just
> hard to understand why.

I agree. You asked me to show how a volatile pointer could be used to
protect a C++ class object and I did so. I didn't say that it was the best,
most readable, or anything like that.

>
>>> BTW, why _exactly_ did you use volatile in your declaration of
>>> g_sharedVector? (Based on the declaration of
>>> InterlockedExchangePointerAcquire, it shouldn't even compile.)
>
> No answer? I really would like to hear what you think volatile
> accomplishes
> here.

Well, there is no "InterlockedExchangePointerAcquire". There's an
"InterlockedExchangePointer", which does declare the parameter as a pointer
to a volatile (and yes, a void*, so g_sharedVector could either be made a
volatile void* or a cast could be used). Also there's an
"_InterlockedExchangePointer_acq" compiler intrinsic only for Itanium, which
also... wow... needs to be given a pointer to a volatile variable (long this
time).

If f2 is not dllexport, and its address is not taken (and it isn't reachable
from any function that has an address taken -- this is going to save you
because it is reachable from some ThreadProc which has an address taken and
passed to CreateThread, ... unless of course f2 happens to be called only
from the main thread). So, what if f2 is WinMain (and has appropriate
WinMain behavior above and below the mutex access)? I'm pretty sure that if
we work at this long enough, the compiler is going to determine through
aliasing analysis that neither lock nor unlock nor g change x, and that it
is safe to optimize away the second access in f1.

>
> To a large extent, this is not even a multithreading issue. It also
> applies
> to single-threaded code.
>
> (My example does assume that f1 and f2 are called sometime, somewhere. If
> one of them isn't, it's not very interesting.)
>

Yes, I assume that at least two threads are running and calling f1 and f2
from different threads, potentially at the same time.


>>It is *not* a performance killer when used correctly. Look at my original
>>example above and note that pvector is not declared volatile, only the
>>shared pointer is. Within the critical section all optimizations are
>>possible.
>
> Before you make claims about your use of "volatile", answer the question I
> posed last time:
>
>>> BTW, why _exactly_ did you use volatile in your declaration of
>>> g_sharedVector? (Based on the declaration of
>>> InterlockedExchangePointerAcquire, it shouldn't even compile.)
>
> This is an important question for you to answer in detail.

Sorry, the correct declaration of g_sharedVector is:
volatile void* g_sharedVector;
or else a cast is needed to make it compatible with
InterlockedCompareExchangePointer. Actually you need a cast either on
either the parameter or the return value, but that's normal for
InterlockedCompareExchangePointer. Typically I would write a templated
wrapper to hide the cast and ensure that the input and return value have
matching type.

Why exactly did I put volatile in the declaration, when the compiler allows
it to be implicitly added? For the same reason that I declare immutable
variables with const -- to prevent them from being used in an inappropriate
context. Also, because it is needed for the alternate syntax which is valid
under VC2005 of a simple assignment instead of a function call.

Anyway, in your example, what do you think would be the performance hit of
declaring x as volatile if, as you seem to think, the compiler cannot
optimize access to x because of the presence of function calls.


Doug Harrison [MVP]

unread,
Aug 21, 2007, 12:48:07 PM8/21/07
to
On Mon, 20 Aug 2007 17:16:11 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>> Common sense dictates using the highest-level abstraction available unless
>> there's a good, specific reason to do otherwise. If, when someone says
>> "mutex", as I repeatedly did, you think "InterlockedXXX", well, it's just
>> hard to understand why.
>
>I agree. You asked me to show how a volatile pointer could be used to
>protect a C++ class object and I did so. I didn't say that it was the best,
>most readable, or anything like that.

To be fair, you introduced it into the conversation. My purpose in asking
you to expand on what you meant by:

>any larger object can be controlled in a threadsafe manner using a volatile
>pointer (which is word-sized) and memory barriers.

was to learn more about what you were claiming for "volatile". (That's why
I also said to imagine uniprocessor or SMP x86 and forget about MBs.) I was
surprised by the InterlockedXXX reply, because no one would use that method
as an alternative to mutexes (except perhaps on an exceedingly rare basis),
which is what I had been referring to for a couple of messages. I was
expecting an answer in terms of mutexes.

>>>> BTW, why _exactly_ did you use volatile in your declaration of
>>>> g_sharedVector? (Based on the declaration of
>>>> InterlockedExchangePointerAcquire, it shouldn't even compile.)
>>
>> No answer? I really would like to hear what you think volatile
>> accomplishes
>> here.
>
>Well, there is no "InterlockedExchangePointerAcquire".

Well, it is what you used in your example. :)

It's also documented in MSDN:

http://msdn2.microsoft.com/en-us/library/ms683611.aspx

>There's an "InterlockedExchangePointer"

That's fine, too. The signatures are the same, so it doesn't affect my
question.

>which does declare the parameter as a pointer
>to a volatile (and yes, a void*, so g_sharedVector could either be made a
>volatile void* or a cast could be used).

No, if your pointer p has the type:

volatile void*

then based on the declaration of InterlockedExchangePointer, &p will
require a cast, too. I was hoping that hinting at the declaration problem
would get you to talk about what you think volatile actually does in the
example you posted WRT to what you said earlier:

>any larger object can be controlled in a threadsafe manner using a volatile
>pointer (which is word-sized) and memory barriers.

(Again, scratch "memory barriers" and talk about uniprocessor or x86 SMP.)

>If f2 is not dllexport, and its address is not taken (and it isn't reachable
>from any function that has an address taken -- this is going to save you
>because it is reachable from some ThreadProc which has an address taken and
>passed to CreateThread, ... unless of course f2 happens to be called only
>from the main thread).

Note that it isn't enough to recognize just CreateThread. It has to
recognize MyCreateThread in my opaque DLL, but I have no way to tell it
MyCreateThread is a "thread creation" function. And that's just the tip of
the iceberg.

>So, what if f2 is WinMain (and has appropriate
>WinMain behavior above and below the mutex access)? I'm pretty sure that if
>we work at this long enough, the compiler is going to determine through
>aliasing analysis that neither lock nor unlock nor g change x, and that it
>is safe to optimize away the second access in f1.

In a nutshell, the compiler would have to find all the functions that
modify x and generate their call graphs. Then it would have to recognize
all the ways for functions appearing in those graphs to become callable
directly or indirectly from lock/unlock. Any of those functions whose
address is passed to some opaque function kills the optimization, because
the opaque function could potentially save the pointer for later use by
lock/unlock. So for example, if f2 is called directly or indirectly by a
Windows message handler, the optimization must be killed since f2 is
reachable from the WndProc, whose address is passed to an opaque Windows
API. This is all very time-consuming and will usually result in a dead-end.
It's a lot easier to assume globals are reachable from opaque functions,
and AFAIK, that's what the compiler does. It gives the desired semantics
for multithreaded programming with synchronization, and it's what I've
observed by constructing the simplest possible example for it to try to
optimize. It doesn't optimize it.

>Sorry, the correct declaration of g_sharedVector is:
>volatile void* g_sharedVector;
>or else a cast is needed to make it compatible with
>InterlockedCompareExchangePointer.

Again, that doesn't help at all. You would still need a cast when you say
&g_sharedVector, because you put the volatile in the wrong place. So what
is volatile supposed to accomplish in your example? How does your example
demonstrate what you said:

>any larger object can be controlled in a threadsafe manner using a volatile
>pointer (which is word-sized) and memory barriers.

What do you think your use of volatile accomplishes in your example?

>Actually you need a cast either on
>either the parameter or the return value, but that's normal for
>InterlockedCompareExchangePointer. Typically I would write a templated
>wrapper to hide the cast and ensure that the input and return value have
>matching type.
>
>Why exactly did I put volatile in the declaration, when the compiler allows
>it to be implicitly added? For the same reason that I declare immutable
>variables with const -- to prevent them from being used in an inappropriate
>context.

But you asserted that using volatile is necessary to prevent the compiler
from performing unsafe optimizations. I'd like you to explain how it does
that in your example. About the "inappropriate context", see below.

>Anyway, in your example, what do you think would be the performance hit of
>declaring x as volatile if, as you seem to think, the compiler cannot
>optimize access to x because of the presence of function calls.

Not just "function calls", but "opaque function calls". Using the
pre-VC2005 volatile means the compiler will never cache the value, so it
goes to memory for every access. The VC2005 and later volatile adds memory
barrier overhead to this, which is even worse. Both represent unnecessary
overhead inside a critical section, where access to that variable is
single-threaded and should be subject to the usual optimizations. In
addition, make x a volatile std::vector or just about any class, and you
have to cast volatile away to use it, because no one declares their member
functions volatile. Besides being a pain, casting volatile away from an
object originally declared volatile and referring to the object through the
non-volatile lvalue is undefined.

Then there's this:

struct X
{
int* const p;
};

Mutex mx;
volatile X x = { new int(0) };

void f()
{
int* p = x.p; // Fine

lock(mx);
// use *p
unlock(mx);

lock(mx)
// use *p
unlock(mx);
}

void f2()
{
lock(mx)
++*x.p;
unlock(mx);
}

Making x volatile doesn't help, because volatile doesn't penetrate very far
at all. That's the sort of thing I was getting at several messages ago. The
compiler won't detect this "usage in an inappropriate context", namely
initializing p with x.p, which is another reason it's a bad idea to try to
hijack volatile for multithreading purposes.

Ulrich Eckhardt

unread,
Aug 22, 2007, 5:50:42 AM8/22/07
to
Ben Voigt [C++ MVP] wrote:
> "Doug Harrison [MVP]" <d...@mvps.org> wrote in message
> news:nefcc3hh5er85j8hd...@4ax.com...
>> On Fri, 17 Aug 2007 15:14:50 -0500, "Ben Voigt [C++ MVP]"
>> <r...@nospam.nospam> wrote:
>>>volatile std::vector* g_sharedVector;
[...]

>> BTW, why _exactly_ did you use volatile in your declaration of
>> g_sharedVector? (Based on the declaration of
>> InterlockedExchangePointerAcquire, it shouldn't even compile.)

C'mon, it's an oversight coming from not consistently putting the
CV-qualifiers to the right of what they affect. I'm pretty sure you
understood what he meant and that he didn't actually compile the code.

Right, but it knows that lock and unlock are functions that affect
multithreading behaviour not only in that they lock a mutex but also that
they present a memory barrier (well, at least according to POSIX) and that
it must not cache the value of 'x' for that reason. Seriously,
multithreading is simply not possible without compiler support, it must
know that some code can be called in a multithreaded context.

Further, it knows that 'f1'/'f2' can be called by multiple threads at once,
so again it can't perform optimisations that affect 'x'.

> Using DLLs to inhibit optimization is broken, Broken, BROKEN!

Thinking that multithreading problems can be fixed by inhibiting any
optimisations is broken. However, when a call goes into a DLL the compiler
must assume (as long as it can't inspect the content) that it could present
a memory barrier.

> Adding "volatile" to the declaration of x fixes the problem.

No, sorry. Firstly, the definition of 'volatile' is too vague to be of any
use in (MT) programming - you read the discussions on cpt and know that its
meaning is mostly compiler-dependant. Secondly, you already have (almost
explicit) memory barriers there from the lock/unlock calls, these are
really enough.

Note that if that wasn't enough, you would have to apply a 'volatile'
qualifier to the whole vector, too, i.e. all the pointers it contains and
even the pointers contained by its elements.

Uli

Doug Harrison [MVP]

unread,
Aug 22, 2007, 12:41:24 PM8/22/07
to
On Wed, 22 Aug 2007 11:50:42 +0200, Ulrich Eckhardt
<eckh...@satorlaser.com> wrote:

>Ben Voigt [C++ MVP] wrote:
>> "Doug Harrison [MVP]" <d...@mvps.org> wrote in message
>> news:nefcc3hh5er85j8hd...@4ax.com...
>>> On Fri, 17 Aug 2007 15:14:50 -0500, "Ben Voigt [C++ MVP]"
>>> <r...@nospam.nospam> wrote:
>>>>volatile std::vector* g_sharedVector;
>[...]
>>> BTW, why _exactly_ did you use volatile in your declaration of
>>> g_sharedVector? (Based on the declaration of
>>> InterlockedExchangePointerAcquire, it shouldn't even compile.)
>
>C'mon, it's an oversight coming from not consistently putting the
>CV-qualifiers to the right of what they affect. I'm pretty sure you
>understood what he meant and that he didn't actually compile the code.

Actually, I did try to compile the code, found I had to change the function
to InterlockedExchangePointer because InterlockedExchangePointerAcquire
wasn't available on my installation, and then discovered, to my surprise,
that it *did* compile. That's why I rather deliberately said "shouldn't
even compile". I figured we'd get to all that later, but so far, we've
gotten only to the need to use InterlockedExchangePointer.

Please note the fact that "it should not compile" is a minor part of the
question. I'm far more interested in what he thinks using volatile
accomplishes for his example.

>Right, but it knows that lock and unlock are functions that affect
>multithreading behaviour not only in that they lock a mutex but also that
>they present a memory barrier (well, at least according to POSIX) and that
>it must not cache the value of 'x' for that reason.

The VC++ compiler has no idea what those functions do. At least I can write
my own lock/unlock functions, put them in a DLL, and VC++ won't optimize
around them. Those functions can even be empty. Also, memory barriers are
not essential to the discussion.

>Seriously,
>multithreading is simply not possible without compiler support, it must
>know that some code can be called in a multithreaded context.

I'd be interested to see an example that demonstrates what you're saying
that I can't reduce to "necessary for correct behavior of single-threaded
code".

>Further, it knows that 'f1'/'f2' can be called by multiple threads at once,
>so again it can't perform optimisations that affect 'x'.

As I remarked earlier, this isn't just an MT issue. It affects ST code as
well. The compiler does not "know" that f1/f2 may be called from multiple
threads. What allows them to be safely called from multiple threads is the
use of a mutex and the fact that the compiler doesn't optimize around the
opaque lock/unlock calls. However, the compiler doesn't "know" that you're
doing multithreaded programming; replace the mutex and lock/unlock with
renamed, do-nothing versions in an opaque DLL, and it'll produce the same
code. Get rid of them altogether, and the compiler won't continue to
"think" you're doing multithreaded programming; instead, it will perform
the optimizations previously inhibited.

>> Using DLLs to inhibit optimization is broken, Broken, BROKEN!
>
>Thinking that multithreading problems can be fixed by inhibiting any
>optimisations is broken.

Actually, the correct behavior falls out of inhibiting optimization around
opaque function calls. If the functions are not opaque, the compiler would
need some other way to determine the functions are unsafe to optimize
around. Because they are opaque, the compiler is unable to prove the things
it needs to prove in order to safely perform the optimization.

>However, when a call goes into a DLL the compiler
>must assume (as long as it can't inspect the content) that it could present
>a memory barrier.

Memory barriers aren't essential to your argument; they don't exist on
single CPU systems or SMP x86 systems. What the compiler has to assume is
that an opaque function can affect "observable" behavior of the "abstract
machine". For my lock/unlock example, with lock/unlock in a DLL, it means
they can use the global variable x, so it must commit any modification of x
to memory before calling these functions, and when those functions return,
it must assume they modified x, so it will have to reload x from memory.
This is exactly what we need from the compiler for the POSIX memory
visibility guarantees concerning lock/unlock, thread creation, and so
forth, and it all falls out naturally when those functions are opaque.

Ben Voigt [C++ MVP]

unread,
Aug 27, 2007, 3:17:34 PM8/27/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:5vkoc3hofoi6h0ibc...@4ax.com...

You're right, I meant:

std::vector* volatile g_sharedVector;

Sorry about that. I thought you were challenging whether volatile was
needed at all, not where I'd put it.

[snip]

> Memory barriers aren't essential to your argument; they don't exist on
> single CPU systems or SMP x86 systems. What the compiler has to assume is
> that an opaque function can affect "observable" behavior of the "abstract
> machine". For my lock/unlock example, with lock/unlock in a DLL, it means
> they can use the global variable x, so it must commit any modification of
> x
> to memory before calling these functions, and when those functions return,
> it must assume they modified x, so it will have to reload x from memory.

If the visibility of x is sufficiently narrow (anonymous namespace) and its
address isn't taken, then the compiler can "prove" that the DLL won't use
it.


> This is exactly what we need from the compiler for the POSIX memory
> visibility guarantees concerning lock/unlock, thread creation, and so
> forth, and it all falls out naturally when those functions are opaque.

Aliasing analysis still applies even when some functions are opaque.

Doug Harrison [MVP]

unread,
Aug 27, 2007, 4:29:36 PM8/27/07
to
On Mon, 27 Aug 2007 14:17:34 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>> Please note the fact that "it should not compile" is a minor part of the
>> question. I'm far more interested in what he thinks using volatile
>> accomplishes for his example.
>
>You're right, I meant:
>
>std::vector* volatile g_sharedVector;
>
>Sorry about that. I thought you were challenging whether volatile was
>needed at all, not where I'd put it.

As I said, "where you put it" was a MINOR part of the question. For
probably the 6th time at least, what do you think it ACCOMPLISHES in the
example you posted? That, of course, is the MAJOR part of the question. :)
I thought it was pretty clear, but if I need to be more specific, here
goes, "Explain, in your own words, what volatile does in your example, why
it's necessary, and what would happen if you were to remove it." Here, I'll
repeat your example for you ("corrected", so to speak):

>std::vector* volatile g_sharedVector;


>
>...
>
>{
> std::vector* pvector = NULL;
>
> // this is a spin-wait, efficient on SMP, but on a single processor
>system a yield should be inserted
> while (pvector == NULL) {
> pvector = InterlockedExchangePointerAcquire(&g_sharedVector, NULL);
> }
>
> // use pvector in any way desired
>

> // with VC2005, can use "g_sharedVector = pvector;" instead
> InterlockedExchangePointerRelease(&g_sharedVector, pvector);
>}

What do you think volatile adds to your example? What do you think would
happen if you removed it altogether? Remember, this is an example of what
you asserted on 8/16, "any larger object can be controlled in a threadsafe


manner using a volatile pointer (which is word-sized) and memory barriers."

(As I did in my reply on 8/17, I again ask you to forget about memory
barriers, which don't apply to uniprocessor and x86 SMP.) What in the world
do you think "volatile" is doing for you here?

>If the visibility of x is sufficiently narrow (anonymous namespace) and its
>address isn't taken, then the compiler can "prove" that the DLL won't use
>it.
>

>Aliasing analysis still applies even when some functions are opaque.

Instead of repeating your assertions, can you provide even one example to
back them up? Here, I'll ask you again:

***** Excerpted from message I posted on 8/20/07

For all these reasons, I think I'm on pretty safe ground (to put it mildly)
when I say that volatile is not required when using synchronization. If you
still disagree, I ask you to produce a counter-example.

I think I'm on equally safe ground WRT what I've said about DLLs. If you
still disagree, I ask you to produce a counter-example.

******

If you choose to answer my questions this time, please do so in context by
replying to my post from 8/20/07, in which I asked them originally.

Ben Voigt [C++ MVP]

unread,
Aug 28, 2007, 1:38:48 PM8/28/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:osa6d3lp2vb5b3k9p...@4ax.com...

It's "volatile-correctness" and prevents the object from being passed
someplace it shouldn't, just like const-correctness does:

class Example
{
char myarray[1000];

void doit() const
{
sprintf(myarray, "%d", 5);
}
};

Because myarray is a pointer-to-const inside a const member function, the
compiler stops me from using it as the first argument to sprintf,
overwriting the data and breaking the "const" contract.

Similarly, because g_sharedVector is declared volatile, taking its address
yields a pointer-to-volatile, which the compiler will not permit to be
passed to a function that accepts a pointer to non-volatile. Interlocked***
can work on non-volatile variables, but volatile variables cannot be used by
anything but Interlocked*** and other functions designed for
synchronization. Just like non-const variables can be read, but marking a
variable as const means that it can only be read.


Doug Harrison [MVP]

unread,
Aug 28, 2007, 5:45:16 PM8/28/07
to
On Tue, 28 Aug 2007 12:38:48 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>It's "volatile-correctness" and prevents the object from being passed
>someplace it shouldn't, just like const-correctness does:
>
>class Example
>{
> char myarray[1000];
>
> void doit() const
> {
> sprintf(myarray, "%d", 5);
> }
>};
>
>Because myarray is a pointer-to-const inside a const member function

>the compiler stops me from using it as the first argument to sprintf,
>overwriting the data and breaking the "const" contract.

To be faithful to what you have been claiming, you should have used a
pointer, not an array. The "corrected" version of your example uses a
volatile pointer, not a pointer-to-volatile:

std::vector* volatile g_sharedVector;

This is perfectly fine:

std::vector* p = g_sharedVector;

Doesn't give you much "protection".

>Similarly, because g_sharedVector is declared volatile, taking its address
>yields a pointer-to-volatile, which the compiler will not permit to be
>passed to a function that accepts a pointer to non-volatile. Interlocked***
>can work on non-volatile variables, but volatile variables cannot be used by
>anything but Interlocked*** and other functions designed for
>synchronization. Just like non-const variables can be read, but marking a
>variable as const means that it can only be read.

The only important thing in all this is your use of InterlockedXXX, which
allowed you to poorly imitate a mutex. Since you admit that InterlockedXXX
"can work on non-volatile variables", would you like to amend your earlier
claim:

>any larger object can be controlled in a threadsafe
>manner using a volatile pointer (which is word-sized) and memory barriers.

An accurate statement would be:

"It is possible to poorly emulate a mutex using pointers and
InterlockedExchangePointer."

However, you've been arguing the necessity of using volatile when
programming with mutexes, or as I sometimes call it, "volatile on top of
synchronization". I hope you understand now that volatile is not even
necessary for the correct operation of your example.

Ben Voigt [C++ MVP]

unread,
Aug 29, 2007, 10:19:14 AM8/29/07
to

"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:va49d3hm62f07nrid...@4ax.com...

> On Tue, 28 Aug 2007 12:38:48 -0500, "Ben Voigt [C++ MVP]"
> <r...@nospam.nospam> wrote:
>
>>It's "volatile-correctness" and prevents the object from being passed
>>someplace it shouldn't, just like const-correctness does:
>>
>>class Example
>>{
>> char myarray[1000];
>>
>> void doit() const
>> {
>> sprintf(myarray, "%d", 5);
>> }
>>};
>>
>>Because myarray is a pointer-to-const inside a const member function
>>the compiler stops me from using it as the first argument to sprintf,
>>overwriting the data and breaking the "const" contract.
>
> To be faithful to what you have been claiming, you should have used a
> pointer, not an array. The "corrected" version of your example uses a
> volatile pointer, not a pointer-to-volatile:
>
> std::vector* volatile g_sharedVector;
>
> This is perfectly fine:
>
> std::vector* p = g_sharedVector;
>
> Doesn't give you much "protection".

There's a guarantee that the actual value of g_sharedVector will be used,
not a cached value. But if you read what I said, it was that
(&g_sharedVector) could not be passed to a function not designed for
volatile access.

>
>>Similarly, because g_sharedVector is declared volatile, taking its address
>>yields a pointer-to-volatile, which the compiler will not permit to be
>>passed to a function that accepts a pointer to non-volatile.
>>Interlocked***
>>can work on non-volatile variables, but volatile variables cannot be used
>>by
>>anything but Interlocked*** and other functions designed for
>>synchronization. Just like non-const variables can be read, but marking a
>>variable as const means that it can only be read.
>
> The only important thing in all this is your use of InterlockedXXX, which
> allowed you to poorly imitate a mutex. Since you admit that InterlockedXXX
> "can work on non-volatile variables", would you like to amend your earlier
> claim:
>
>>any larger object can be controlled in a threadsafe
>>manner using a volatile pointer (which is word-sized) and memory barriers.
>
> An accurate statement would be:
>
> "It is possible to poorly emulate a mutex using pointers and
> InterlockedExchangePointer."
>
> However, you've been arguing the necessity of using volatile when
> programming with mutexes, or as I sometimes call it, "volatile on top of
> synchronization". I hope you understand now that volatile is not even
> necessary for the correct operation of your example.

The release part of the operation was able to use a volatile write in VC2005
and avoid the function call. That wouldn't be correct without volatile.


Doug Harrison [MVP]

unread,
Aug 29, 2007, 3:41:25 PM8/29/07
to
On Wed, 29 Aug 2007 09:19:14 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

Oh, I understood perfectly, but I am struggling to think of even one
function you could reasonably be worried about. To add to what I said last
time, this is also perfectly fine:

std::vector* p = whatever;
g_sharedVector = p;

You're worried about passing &g_sharedVector to some hypothetical functions
which might assign to it through a non-volatile lvalue, yet you're not even
protecting it against simple assignment, and simple assignment is
incompatible with your InterlockedXXX usage.

Also, you've never once mentioned the vector whose address is used to
initialize g_sharedVector. You need to avoid using that object outside the
critical section. The same situation exists when using a mutex, but if you
use a mutex, you don't need g_sharedVector, you don't have to worry about
your method's unbounded busy-waiting, you don't have to use an extra local
variable and a loop to enter the critical section, you're more portable,
and so forth.

Your notion of "volatile-correctness", which you mentioned last time, is
nowhere near as strong as what Andrei A was proposing several years ago,
and his idea hasn't exactly caught on. At least he was concerned with using
volatile to protect the object, i.e. the elusive vector in your example, to
help prevent its use outside the critical section. Your idea of
"volatile-correctness" merely protects a pointer which doesn't even exist
when using normal synchronization techniques, such as the mutexes I've been
talking about. It doesn't prevent you from assigning g_sharedVector's value
to a non-volatile pointer, nor does it prevent calling vector member
functions through g_sharedVector itself. It doesn't even prevent you from
directly assigning a new value to g_sharedVector. In summary, it provides
unnecessary, useless, false "protection" for something that shouldn't even
exist in the first place.

>The release part of the operation was able to use a volatile write in VC2005
>and avoid the function call. That wouldn't be correct without volatile.

The "release" part of the operation (on those platforms where it actually
has meaning) occurs due to using the InterlockedXXX API. It is not
necessary for *you* to declare *your* pointer volatile.

Ben Voigt [C++ MVP]

unread,
Aug 29, 2007, 6:31:15 PM8/29/07
to
>>The release part of the operation was able to use a volatile write in
>>VC2005
>>and avoid the function call. That wouldn't be correct without volatile.
>
> The "release" part of the operation (on those platforms where it actually
> has meaning) occurs due to using the InterlockedXXX API. It is not
> necessary for *you* to declare *your* pointer volatile.

A volatile assignment works perfectly well for the "release" operation:

std::vector* volatile g_sharedVector;

...

{
std::vector* pvector = NULL;

// this is a spin-wait, efficient on SMP, but on a single processor
system a yield should be inserted
while (pvector == NULL) {
pvector = InterlockedExchangePointerAcquire(&g_sharedVector, NULL);
}

// use pvector in any way desired

g_sharedVector = pvector;
}


Doug Harrison [MVP]

unread,
Aug 30, 2007, 1:45:42 PM8/30/07
to
On Wed, 29 Aug 2007 17:31:15 -0500, "Ben Voigt [C++ MVP]"
<r...@nospam.nospam> wrote:

>>>The release part of the operation was able to use a volatile write in
>>>VC2005
>>>and avoid the function call. That wouldn't be correct without volatile.
>>
>> The "release" part of the operation (on those platforms where it actually
>> has meaning) occurs due to using the InterlockedXXX API. It is not
>> necessary for *you* to declare *your* pointer volatile.
>
>A volatile assignment works perfectly well for the "release" operation:

You used the InterlockedXXX API in your example. As I said, you don't need
to declare your pointer volatile for that to work. If you want to change
the example and get rid of InterlockedXXX for the "release", I suppose it's
fine in VC2005 and later as long as the pointer is volatile. Note well,
however, that the volatile semantics MS introduced in VC2005 are quite
extraordinary (in at least two ways), and given that InterlockedXXX works
fine all by itself, without volatile, one would really have to be pining
away for volatile to use this unwise combination of InterlockedXXX and the
VC2005 volatile.

>std::vector* volatile g_sharedVector;
>
>...
>
>{
> std::vector* pvector = NULL;
>
> // this is a spin-wait, efficient on SMP, but on a single processor
>system a yield should be inserted
> while (pvector == NULL) {
> pvector = InterlockedExchangePointerAcquire(&g_sharedVector, NULL);
> }
>
> // use pvector in any way desired
>
> g_sharedVector = pvector;
>}

Your example, as originally stated, was intended to illustrate your earlier
claim:

>any larger object can be controlled in a threadsafe
>manner using a volatile pointer (which is word-sized) and memory barriers.

If you use InterlockedExchangePointer for the "release", which you did
until now, then you don't need volatile. (And you need the very special
VC2005 volatile for your new idea to work.) For uniprocessor and x86 SMP,
memory barriers aren't relevant. It should finally be noted that pointer
variables aren't essential to the technique you presented.

0 new messages