Topic: Portability. Modules. WAS: Are all Windows programs ill-formed?


Author: kanze@gabi-soft.fr (J. Kanze)
Date: 1996/02/22
Raw View
In article <UPMAIL04.199602192354270852@msn.com> "Eugene Lazutkin"
<INT@msn.com> writes:

|> I don't care about the actual implementation.  Virtually there is a main().
|> I know that all my static constructors will be executed BEFORE main(),
|> WinMain() or any other head procedure.  It is important.  It doesn't make
|> any difference for me whether or not main() is the first called subroutine.
|> In fact, in most cases it is not true.  Almost all platforms call some
|> stub before your main().

You know something that is *NOT* guaranteed by the draft standard.
There is no guarantee that static constructors are executed before main,
although in fact, this is the case for all implementations I am familiar
with, at least as long as the static variables are not in DLL's.  (And:
I've got more than a little code which depends on this.  Despite it not
being guaranteed.)
--
James Kanze           (+33) 88 14 49 00          email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs Bourgeois, 67000 Strasbourg, France
Conseils, itudes et rialisations en logiciel orienti objet --
              -- A la recherche d'une activiti dans une region francophone
---
[ To submit articles: Try just posting with your newsreader.  If that fails,
                      use mailto:std-c++@ncar.ucar.edu
  FAQ:    http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: James Kanze US/ESC 60/3/141 #40763 <kanze@lts.sel.alcatel.de>
Date: 1996/02/13
Raw View
In article <01BAF61B.3131B100@dino.int.com> Eugene Lazutkin
<eugene@int.com> writes:

|> On  Tuesday, February 06, 1996 9:18 PM,
|> rich@kastle.com (Richard Krehbiel) wrote:

    [...]
|> Another point of non-portability is #pragmas.  Static containers are
|> almost useless unless you can specify the order of their
|> construction/destruction.

This depends on the design of the container.  See below for two ways
of handling this.

|> Borland provides you with #pragma
|> startup/exit (am I correct with names?). With this tool you can specify
|> a priority. It is not a ultimate medicine but it helps a lot!

It helps make your programs non-portable, you mean:-) (as you point
out).  There are better solutions.

|> If you use a static container with some statically included items (by
|> their constructors), your objects will be well-formed automatically in
|> most cases (I mean practical cases):

|> static Container  list(); // The constructor of this list has higher priority
|>      // than item's constructor. You do this trick
|>      // with a #pragma.
|> static Item item1( list ); // All items are located in different files.
|> static Item item2( list ); // The item's constructor includes an item
|>     // into a list.
|> /* ... */
|> static Item itemN( list );

|> // file with the main() function
|> void main() {
|>  /* ... */
|>  // lets check all items
|>  CHECK_ALL_ITEMS( list );
|>  /* ... */
|> }

I use this construction all of the time.  Depending on the complexity
of the problem, I have two solutions.

The oldest one, rarely used except for the simplest cases today, is to
design the list such that the initial initialization to 0 is adequate,
and give it a special no-op constructor.  Thus, for example, my
command line parser class maintains a list of options (staticly
declared objects); a NULL pointer signifies an empty list, and the
constructor does nothing.  Thus, it doesn't matter if the options are
constructed (and register themselves) before the list.

The more frequent solution today is to have a function returning a
reference to a local static object.  The object will be constructed
the first time the function is called.  The enrolment functions in the
Item's call the function to get the actual list object.

I often encapsulate the above solution in a SharedList interface
class.  All instances of the SharedList actually refer to the same
list object.  The SharedList only contains a pointer to the actual
list object, and forwards all requests to it.  The pointer is
initialized by calling the function above in the constructor.

|> Alternative way: include all items into the container manually and
|> don't forget to do this initialization every time when you use this
|> container in your program.

This is extremely error prone (as you also point out).  If you are
willing to accept that all of the objects are known in the same
compilation unit, you might as well define them there, and let the
guaranteed order of construction within the compilation unit do the
trick.

Both of my proposed solutions for this problem should be portable to
compilers which initialize the static variables before main.  This was
not required in the Sept., 1995 draft, but is the case for most
compilers as long as DDL's are not involved.  If the static data is in
a DDL, you must make sure that the DDL is loaded before main.
(Personally, I would try and avoid non-local static data in a DDL.)
--

James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils,    tudes et r   alisations en logiciel orient    objet --
                -- A la recherche d'une activit    dans une region francophone


      [ Articles to moderate: mailto:c++-submit@netlab.cs.rpi.edu ]
      [  Read the C++ FAQ: http://www.connobj.com/cpp/cppfaq.htm  ]
      [  Moderation policy: http://www.connobj.com/cpp/guide.htm  ]
      [      Comments? mailto:c++-request@netlab.cs.rpi.edu       ]





Author: Eugene Lazutkin <eugene@int.com>
Date: 1996/02/08
Raw View
On  Tuesday, February 06, 1996 9:18 PM,
rich@kastle.com (Richard Krehbiel) wrote:
> fjh@munta.cs.mu.OZ.AU (Fergus Henderson) wrote:
>
> >I did not claim that any or all Windows compilers were not conforming.
> >I simply pointed out what they were required to do in order to conform.
>
> >I did say that
>
> >| I think the problem is due to Microsoft overlooking the C and C++
> >| standards, not vice versa.
>
> >but the problem I was referring to was not that Microsoft's compilers
> >don't conform to the standard (most likely they do) but rather that
> >Microsoft encourages people to write non-portable, non-standard-conforming
> >code which is then subject to the usual problems of vendor lock-in.
>
> YES, YES, YES.  This is IT.
>

If you use Windows GUI => your program is non-portable by definition,
because your use vendor-specific libraries.  Under these circumstances
I personally don't care about portability and how my main function looks
like.

This is not a matter of a portability! If you don't have a portable library
(GUI, math, network & Co.), your program will be non-portable anyway.

Current (and future :-( ) standard doesn't cover a very important topic:
libraries.  A library is not a bunch of somehow related (or unrelated)
functions. Sometimes you need initialize your library and clean up after
termination.  Shared libraries raise more complex questions.  Of course,
I want to enjoy the benefits of DLLs (dynamic link libraries). In this case
my library will be Windows-specific.

If I want to write a portable program in Windows, I write console-mode
program (without any GUI) and use just standard iostream.h for an
interaction with a user. It is a portable way, but usually it is not
suitable for end users.

About the main() function.  A Windows compiler (MS, Borland, Symantec,
Watcom) doesn't check its presence. Linker does. It knows the name of
an entry point.  Standard library contains some sort of proxy with this
name which calls main() or whatever. So it is a matter of a proxy for a
linker.  You may think that main() is buried in the standard Windows
library and it will call WinMain(). That's it, you use the main()
implicitly.

BTW, the same can be applied to the Unix (MacOS) platform.  X-based
(Motif-based, Fresco-based,...) program are not portable either unless
you have the specific GUI implementation for your target platforms.

Another point of non-portability is #pragmas.  Static containers are
almost useless unless you can specify the order of their
construction/destruction.  Borland provides you with #pragma
startup/exit (am I correct with names?). With this tool you can specify
a priority. It is not a ultimate medicine but it helps a lot!

If you use a static container with some statically included items (by
their constructors), your objects will be well-formed automatically in
most cases (I mean practical cases):

static Container  list(); // The constructor of this list has higher priority
     // than item's constructor. You do this trick
     // with a #pragma.
static Item item1( list ); // All items are located in different files.
static Item item2( list ); // The item's constructor includes an item
    // into a list.
/* ... */
static Item itemN( list );

// file with the main() function
void main() {
 /* ... */
 // lets check all items
 CHECK_ALL_ITEMS( list );
 /* ... */
}

Alternative way: include all items into the container manually and
don't forget to do this initialization every time when you use this
container in your program.

static Container  list();

static Item item1(); // All items are located in different files.
static Item item2();
/* ... */
static Item itemN();

// file with the main() function
// you have to include here the declarations of all items!
void main() {
 /* ... */
 // initialization of list, all items should be visible here!
 list.include( item1 );
 list.include( item2 );
 /* ... other includes ... */
 list.include( itemN );
 /* ... */
 // lets check all items
 CHECK_ALL_ITEMS( list );
 /* ... */
}

If you add a new item, you should add one more file in 1st case.  In
2nd case you have to modify several places in your program. And this is
a potential source of errors.

Of course, this is just an example, but this is a real-live example. I
meet these cases every my project.

I don't think that WinMain() is a serious source of incompatibility and
non-portability.  I think that the main source is vendor-specific
libraries (we can't do anything with that) and the obvious weakness of
C++ standard in the modularization of programs. We don't have the
strong definition of the module, how it can be controlled by us
programmers (initialization, deinitialization, reusing by different
threads and/or programs) and how we can create well-formed static
structures and conglomerates.

If we don't solve these problems, we will have non-portable
applications. This is not a just language level.  This is a level ov
C++ machine, how it works, C++ environment.

Thanks for patience


Eugene Lazutkin
eugene@int.com
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  Moderation policy:
  http://reality.sgi.com/employees/austern_mti/std-c++/policy.html. ]





Author: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1996/02/20
Raw View
In article <UPMAIL04.199602192354280583@msn.com> "Eugene Lazutkin"
<INT@msn.com> writes:

|> On  Tuesday, February 13, 1996 7:48 AM,
|>  James Kanze US/ESC 60/3/141 #40763 <kanze@lts.sel.alcatel.de> wrote:
|> > In article <01BAF61B.3131B100@dino.int.com> Eugene Lazutkin
|> > <eugene@int.com> writes:

|> > The oldest one, rarely used except for the simplest cases today, is to
|> > design the list such that the initial initialization to 0 is adequate,
|> > and give it a special no-op constructor.  Thus, for example, my
|> > command line parser class maintains a list of options (staticly
|> > declared objects); a NULL pointer signifies an empty list, and the
|> > constructor does nothing.  Thus, it doesn't matter if the options are
|> > constructed (and register themselves) before the list.

|> It doesn't help to create a simple double-linked ring.  Not all
|> containers can be built on a single-linked list or a tree structure.
|> E.g., any member of a double linked ring can exclude itself from the
|> list automatically by its destructor at any time.  You can't do it
|> with single-linked list or with double-linked list (not ring).

I've used it for double-linked lists:-).  It does mean 1) that the
list itself is special, and 2) a little bit more code to check the
special case (linked to head/tail).  In the case of 1, this is
generally the case anyway.  For 2, in the cases I've used it,
performance hasn't been a problem.

More generally, most of the classes I have that use this are
associative arrays.  The actual array is dynamically allocated (on the
heap), and the only static data is a pointer.  The access routines
check for NULL, and create the array as needed.

|> BTW, who frees the allocated memory?  When?  How can I specify the
|> order of destruction?  ObjectSpace's STL has problems with that.
|> The program using this STL will leak.  They can't eleminate this
|> problem.  OK, they have a work around.  But it is very inefficient:
|> they use per-object allocators instead of per-class.

Order of destruction is undefined.  In general, IMHO, destructors
shouldn't be doing much, so that order of destruction is not a
significant problem.

In most cases of static maps, I simply let the memory leak.  Since I
couldn't free it before the static destructors run anyway, there's
really no point; once the process finishes, the OS recovers it anyway.
(I know that this isn't particularly elegant, but solving real
problems has had priority until now.)

A potential solution would have the users of the class enrol/deenrol.
When the last user deenrols, clean up.

|> > The more frequent solution today is to have a function returning a
|> > reference to a local static object.  The object will be constructed
|> > the first time the function is called.  The enrolment functions in the
|> > Item's call the function to get the actual list object.

|> Does this static object have any constructors?  Or its components?
|> Is it based on some class without constructors?  If not, who will
|> be initialize it?  And how?  Who will deinitialize it?  A lot of
|> problems.

Of course, it has constructors.  And destructors.  The constructors
are called the first time the function is called.  Note that this is
the only solution (that I know of, at least) that is fully conforming,
and works even if the implementation does not initialize everything
before main.

There may still be problems with order of destruction.  According to
the current draft (Jan. 96), all static objects (including those with
block scope) must be destructed in the reverse order of their
construction, so there is at least one guarantee, although probably
not the one you need, at least presuming that order of construction
refers to the order the constructors were entered, and not the order
they were left.

Note that most compilers do not yet implement this order of
destruction.  If I remember right, for example, the Sun compiler
(4.0.1) will destruct all file (namespace) scope static objects first,
and then block scope statics.

|> Of course, both these solutions work just fine in certain specific
|> cases.  But it is not a generic answer.  It is a trick.

I'll accept this for the first solution.  I only use it in special
cases, and in modern code, practically not at all.  The second
solution is not a trick, however; it is the standard generic answer,
which works in all cases, and ensures destruction.  It may lead to
problems in the order of destruction, but no more than any other
solution.  The best solution here is to not allow destructors to
access *other* static objects.

|> > I often encapsulate the above solution in a SharedList interface
|> > class.  All instances of the SharedList actually refer to the same
|> > list object.  The SharedList only contains a pointer to the actual
|> > list object, and forwards all requests to it.  The pointer is
|> > initialized by calling the function above in the constructor.
|> >
|> > |> Alternative way: include all items into the container manually and
|> > |> don't forget to do this initialization every time when you use this
|> > |> container in your program.
|> >
|> > This is extremely error prone (as you also point out).  If you are
|> > willing to accept that all of the objects are known in the same
|> > compilation unit, you might as well define them there, and let the
|> > guaranteed order of construction within the compilation unit do the
|> > trick.

|> This is C++, not Pascal.  We have separate units.  I think it is too
|> strong requirement to put some logically different items together.
|> I think it is too error-prone.

I basically agree, although if the objects aren't related in any way,
then the order of construction between them shouldn't matter:-).  In
fact, of course, I use the registry idiom in almost all cases where I
have something to parse, including the command line options.  I find
it nice that the option is declared near its point of use, and that no
other code (including main) knows about it.  I also find it nice that
I can add a command to a parser without changing a single line of
existing code.
--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils, itudes et rialisations en logiciel orienti objet --
                -- A la recherche d'une activiti dans une region francophone
---
[ To submit articles: Try just posting with your newsreader.  If that fails,
                      use mailto:std-c++@ncar.ucar.edu
  FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
  Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
  Comments? mailto:std.c++-request@ncar.ucar.edu
]