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

War stories (very long)

3 views
Skip to first unread message

Mark Woodruff

unread,
May 8, 1993, 2:57:46 AM5/8/93
to
This is the report based, in part, to the request I made earlier
for war stories related to C++, Ojb-C, and Eiffel. This is the
ascii verison of the original WordPerfect file, so its a tad bit
hard to read.
--cut here--


War
Stories


A Report from the Front Line
Comparing C++, Eiffel, and Objective-C


Mark Woodruff
May 8, 1993
Table of Contents

Introduction 1

Method 2
Language Selection 2
Linguistic Philosophies 3
Shared Philosophy--What We Can All Agree On 4
C++: Objects for Systems Programmers 4
Objective-C: Stepstone Toward a New Era 8
Eiffel: A Better Mousetrap 10
Consequences 15
C++ 16
Objective-C 19
Eiffel 22

Results and Discussion 25

Conclusion 26

References 27

Introduction

In the 12 years since Smalltalk-80 first popularized
Object-Oriented Programming (OOP), research has shifted from
answering paradigm-related questions to answering applicative
questions. One important question now under consideration is
which features of the paradigm are necessary and effective in a
language capable of addressing modern programming problems.

This research has produced an overwhelming number of new
object-oriented languages, each exploring the efficacy of certain
aspects of the paradigm. The goal of this study is to select a
few languages (C++, Objective-C, and Eiffel) representing the
current state-of-the-art in practical languages and evaluate them
to determine the impact of the adoption of various features of
the object-oriented paradigm--how well they really work. Only
practical languages were considered, those intended for
developing actual software as opposed to testing hypotheses in
research.

Given the current trend of trying to adopt one general-purpose
language for programming (the Ada approach) rather than creating
many special-purpose languages, this study is important for both
determining if such an approach can be used with object-oriented
languages, and finding which theoretical ideas bear the most
fruit in when put into practice. This research is also critical
in the future direction of software development as language
choice invariably affects both the design process and how we
think about solving programming problems (George Orwell's thesis
revisited).

In evaluating the subject languages it becomes clear that just as
no one conventional language has proven adequate for all
programming tasks, so no one OOP philosophy or language will be.
Instead we must realize the limitations of each of our languages,
use them only when appropriate to the problem at hand, and focus
on ways for languages to communicate freely to make multi-lingual
programming effective.

Method

In this study, I tried to use first-hand information to make
evaluations, using the language authors' material for
philosophical and linguistic analysis and actual users'
experiences ("war stories") for impact assessment. I placed
emphasis on evaluating the success of any unique ideas adopted
from the object-oriented paradigm and the effect of any
constraining notions. This section details the specific methods
for:

o selecting which languages to study

o analyzing those languages to determine their philosophy

o observing the effects of the decisions made by the
languages' designers, including those involving feature inclusion
and constraints

o showing the consequences.

Language Selection

Object-oriented languages have developed into three groups:

o Holistic languages--object-oriented languages which
only function in special environments:

Actor CLOS Flavors LOOPS Methods Smalltalk

o Enhanced conventional languages--languages using a
traditional procedural language as a linguistic base, such as C,
with added object-oriented features:

C++ Objective-C OOPS Object Pascal

o New conventional languages--object-oriented languages
designed to operate in existing environments:

Eiffel

Without exception, the holistic languages, while providing the
purest examples of object-orientation, cannot be applied to
general programming problems in a variety of real environments.
(It would be hard to imagine Smalltalk running under MVS; CLOS
might because batch versions of LISP are available.)

From the remaining two categories, two enhanced languages (C++
and Objective-C) were chosen based on their popularity and
availability on a wide variety of platforms. These two languages
also embody the philosophies expressed in the other enhanced
conventional languages, thus effectively representing the
effective state-of-the-art. I am not aware of any novel
object-oriented features present in another enhanced conventional
language that are not in either or both of these languages.

The final language, Eiffel, was chosen both because it is the
only example of a new object-oriented language for existing
environments and because it introduced a host of novel ideas to
the object-oriented community.

Linguistic Philosophies

I was fortunate in that each of the language's authors has
published commentary on design decisions. This information
provided an invaluable insight into the designers' philosophies.
Beyond this, I analyzed every feature in each language, looking
for object-oriented features that might expand on the publicly
stated philosophy or uncover important differences between stated
(or intended) ideology and actual practice.

To uncover the pool from which each designer drew water, I delved
into periodicals to assess the height and width of the
object-oriented paradigm and how it has changed through the
years. Just as the great Philosophers produced widely diverging
analyses of common themes such as Love or Justice, so these three
languages used this common pool (primarily based on Smalltalk and
Simula) and their own insights on real-world programming to
produce their unique overall philosophies. This commonality was
used to refine the list of features to look at only to the
philosophical differences; implementation differences of the same
idea were considered as one and treated as such.

Linguistic features not related to object-orientation were
largely ignored. Eiffel has many linguistic features (e.g.
assertions) not directly related to object-orientation, but these
and other similar features in the other languages were glossed
over as not germane. Likewise, archaic residuals from C were
disregarded except as illustrations of the consequences of
choosing an existing language as a base.

The resulting lists of features and the philosophies behind them
are detailed in the next sections, first with common features,
then differences.

Shared Philosophy--What We Can All Agree On

All three languages share a common core of the basic ideas of
object-orientation. The basic programming unit is the class, an
implementation of an abstract data type (ADT). Classes may be
open, allowing direct access to their data, or closed hiding the
implementation completely inside a barricade of functions.
Abstract classes, with no instances, are supported.

The various operations that can be carried out on an ADT are
represented by procedures or functions (although their names
within the languages vary). These procedures are bound to the
caller either statically or dynamically as the programmer
requests. Classes may inherit features (including variables and
routines) from other classes and they may encapsulate other
classes.

Objects are references to instances of classes. They include
both the data and the valid operations on the data. Direct
instances of objects, rather than references to them, can also be
created.

In addition to these, all three languages share a commitment to
operating in traditional compiler-oriented environments and
interfacing with existing code. They are strongly typed and
place much emphasis at achieving acceptable performance given the
additional overhead of dynamic binding.

C++: Objects for Systems Programmers

"C++ was designed primarily so that the author and his friends
would not have to program in assembler, C, or various modern
high-level languages." [6, 4] In contrast to Eiffel and
Objective-C, which were designed with a grand scheme in mind to
affect the way we program, C++ was grown at home to enhance, not
supplant, traditional programming practices. C++ aims to make it
easier and more enjoyable to write the same type of code we have
been writing in C for years.

At this, C++ succeeds. Unlike any major new language before it,
C++ has almost completely replaced its successor in the course of
a few years. Thousands of programmers have switched to using C++
because it is completely compatible with C and their existing
code base is usable as-is. C++ has become the de facto
programming language for both the Unix and MS-Dos (Windows)
environment. Based on its adoption, C++ is the most successful
computer language yet invented.

Philosophy This success is a direct result of the
philosophy that produced C++ (detailed in [6] and [7]):
simplicity, compatibility, and efficiency. Simplicity, in
Stroustrup's view, consists of finding the minimal set of
enhancements to his base language that accomplish his end; the
larger environmental and programming issues must be ignored.
Compatibility requires C++ to be eminently and immediately useful
for any C programmers. Existing code bases must be maintained
and new language features cannot get in the way of existing code.
Finally, the functioning of C++ in the areas that require
low-level and low-overhead programming: operating systems,
device drivers, and the like demands efficiency.

Parallelling this linguistic philosophy is a programmatic
philosophy. Programmers are (still) encouraged to "think close
to the machine" in solving problems. The object-oriented
features serve as abstraction techniques provided to hide the
implementation details that such low-level thinking gives birth
to. C++ presents the same world to programmers that C did:
bits, bytes, and pointers. Classes and objects are tools for
organizing and maintaining code, but little more.

This all makes sense given Stroustrup's target audience:
individual programmers. Stroustrup is a programmer working for a
company that encourages writing software for programmers (in the
Unix tradition). His goal is not an environment for
hundred-million line generational projects but to enable "a
single person to cope with 25,000 lines of code." [7, 7]

C++ does not even really strive to be an object-oriented
language. Rather, it adopts features from the object-oriented
paradigm in an attempt to improve traditional programming
efforts. Thus Stroustrup's approach resembles the ANSI COBOL 8X
standard committee; he surveys object-orientation looking for
specific features to adopt that would significantly improve the
language while retaining its fundamental character. So he gives
the following methodology for his design:

[1] All features must be cleanly and elegantly integrated into
the language.

[2] It must be possible to use features in combination to
achieve solutions that would otherwise have required extra,
separate features.

[3] There should be as few spurious and "special-purpose"
features as possible.

[4] A feature should be such that its implementation does not
impose significant overheads [sic] on programs that do not
require it.

[5] A user need only know about the subset of the language
explicitly used to write a program. [7, 15]

The first three of these are general design considerations. The
next to last is the C++ efficiency mandate. The final method
deserves some comment, since it recalls one of the PL/I design
criteria that has long been rejected by other designers.

C++ encourages the use of a subset of C++ where appropriate.
This notion actual works for C++ in juxtaposition to PL/I
because:

o The language is fully usable without them (as an improved
C). This stands in contrast to PL/I where, for example, an
understanding of the promotion of data types was required before
reliable programs could be written. A true subset of
understanding of PL/I was never possible.

o The features are highly isolated and independent.
Exceptions, templates, and overloading may be ignored, for
example, while retaining the essence of the object-oriented
paradigm.

o The language was designed over time so that natural subsets
appear at the junctures where features were added.

Features Although not explicitly intending to, Stroustrup
has managed to create a very rich object-oriented language, one
that has grown much richer as users demand new features. Sharing
with the other two languages the common object-oriented model
given above, it allows for classes, which are operationally
equivalent to types, methods as implemented as member functions
bound to classes, and inheritance. Beyond this core, C++ adds
additional enhancements to promote reusability, information
hiding, and fast performance.

Reusability The reusability enhancements include:

o Multiple inheritance--allows classes to be defined in terms
of more than one class.

o Constrained generics (Templates)--creates a family of
classes with identical functions operating on different types of
data (e.g. list of integers, list of characters, etc.)

o Full operator overloading--creates a consistent interface to
all types. Any type for which addition would be valid, for
instance, could create an + method. Additional methods could be
created to provide seamless addition of existing types to the new
one.

o Abstract classes--enables common protocols to be established
for a hierarchy of classes. The use of pure virtual functions
also protect the abstract classes from ever being implemented.

o Shared variables--enables instances of classes and instances
of derived classes to communicate and share common data promoting
class extension. Virtual base classes extend this by allowing
objects to contain only one set of variables from a base class
even if the base class is included several times from indirect or
multiple inheritance.

Information hiding Information hiding is promoted by
advanced access control. Members may be declared to be public
(total visibility), private (no visibility outside class and
friends), or restricted to class and sub-classes. Special cases
are handled by enabling a foreign class to be a friend to another
class so that the foreign class can access the original class's
internal structures.

Performance Performance is enhanced by trading generality
for speed. Techniques include:

o Function resolution--since function overrides are only
possible on virtual functions, normal functions can be called at
the same speed as C functions.

o Direct virtual function indexing--critical information for
resolving virtual functions is collected at compile time and used
to build a vtable for each class with virtual functions. Each
virtual function is assigned a unique slot in the table so that
all functions derived from the base virtual function map onto the
same slot. This allows functions to be called by merely indexing
into the table to find the function address and calling it as if
a C function.

o Inlining--converts any function (including virtual ones)
into macros by copying the code body directly inline with the
caller's code, thus saving the overhead of a function call.

Miscellaneous Beyond these features, C++ includes some
miscellaneous improvements that are noteworthy but are only
incidentally related to the object-oriented nature of C++:

o Exception handling--promotes robustness by establishing
exception conditions for classes which may be triggered or caught
by active classes.

Local variable storage management--local variables and arguments
are automatically created at function entry and destroyed at
function exit. This allows for both a sophisticated
call-by-value for objects and easier storage management without
garbage collection.

Objective-C: Stepstone Toward a New Era

Objective-C is a conservative hybrid of (conventional) C and
Smalltalk-80 with a radical purpose: to bring on the "Software
Revolution" that will allow the mass production of software much
as the Industrial Revolution allowed the mass production of
hardware. In Objective-C, Brad Cox strives to create a language
which would allow the creation of "Software-ICs", totally
reusable packages of software that could be used in any software
system as easily as hardware ICs can be used in building computer
systems. [3]

Objective-C stands as an evolutionary intermediate language
designed to facilitate moving from conventional design
methodologies toward more object-oriented systems. Its adoption
as the programming language for the NeXT operating system
demonstrates its effectiveness in straddling these two worlds.

Philosophy Objective-C is not the product of particular
language philosophies but merely a byproduct of a much larger
design philosophy. Objective-C exists only to facilitate using
this design philosophy and must be considered in this light.

For more than twenty years software development is said to have
been in a "software crisis." Although numerous advances in
language theory, compiler technology, and overall development
wisdom have significantly improved programmer productivity, we
still are not capable of meeting the need for creating new
programs. Further compounding the problem is our inability to
write systems that respond well to change. The majority of a
program's life-span and cost is still spent maintaining and
updating existing systems to reflect changes in customer
expectations or environment.

Several solutions to this problem have been proposed and tried.
New languages such as fourth-generation languages and
knowledge-based systems have come and gone without significant
impact. Structured data-driven design methods have improved the
reliability of our software, but not by the orders of magnitude
necessary to combat the crisis. Object-oriented design improves
on this further, but shows no sign of radically improving the
situation, given its base in existing Abstract Data Type theory.

Software-ICs What Cox proposes will remedy the crisis is a
new design system based on the adoption of the mass production
techniques in the physical world applied to software. The root
of this system is the adoption of the Software-IC for packaging.
Where structured design focused on building modules with low
coupling and high cohesion, this design method seeks to build
reusable packages of classes that can be moved between languages,
compilers, environments, and hardware--across programs, projects,
and companies. These reusable packages parallel interchangeable
parts in the real world; Cox hopes these will alter software
development as radically as the industrial revolution altered
tool development.

Cox uses the term Software-IC for a class or collection of
classes with certain characteristics that promote their reuse.
Software-ICs, like hardware ICs, are highly specialized, but
application-independent packages, with well-defined interfaces
(listed on "specification sheets"). Competing ICs can exist and,
so long as they follow the same interface, can be exchanged.
More complicated ICs can be built from simpler ones allowing the
equivalent of "board level" packages to be built.

Where Objective-C fits in is as a language, but not the language,
that supports Software-IC development. Software-ICs require
fully dynamic binding, to allow them to be plugged into systems
at will, and the shared object-oriented core discussed above.
Therefore Objective-C support this. It does not endeavor to fix
any problems in C, but uses it as is for the same reasons C++
does (installed code base). Languages as CLOS and Smalltalk-80
can be used to develop Software-ICs as well.

In essence, to Cox, language issues are moot so long as they
support Software-ICs.

Features The features of Objective-C directly follow from
this philosophy with details based on ease of implementation.
Objective-C retains all of the C language, adding only a single
new type, the (object) id, and one new operation, the message.
These are taken directly from Smalltalk-80 with implementation
details adapted as needed. [4] Objective-C supports the common
object-oriented facilities listed above as well as:

o Objects as represented as pointers to data

o Factory (class) methods as well as instance methods

o Static binding by declaring pointers to class instances
rather than objects

o Public (global) instance variables

o Persistence ("activation" and "passification") is fully
supported in a platform-independent format. Cox sees
applications such as a CASE system written in an object-oriented
database on one machine being used to evaluate and maintain a
system executing on another through this feature.

o Single inheritance as Cox feels that the problems created by
multiple inheritance in specifying the definition of a class
defined using multiple inheritance outweighs the implementation
advantages.

o C data types are preserved in their non-object form; no
direct object form is provided.

In essence, Objective-C is C with as much Smalltalk thrown in as
Cox could fit. His interest in creating it lay in providing a
tool to produce Software-ICs using conventional environments. As
a result, a further discussion of features will be deferred as it
would truly be a discussion of Smalltalk, which is not germane.

Eiffel: A Better Mousetrap

When Alexandr Gustave Eiffel looked at the Paris skyline and
envisioned a 984 foot cast-iron tower he saw the past but looked
toward the future. He reasoned that the future demanded new
construction techniques with new materials. By faith he built
his namesake tower, looking forward to a city with new
foundations, whose architect and builder was yet to be born.
Eiffel never saw all the products of his new techniques, but only
welcomed them from a distance. So we commend him for his vision,
although his peers ridiculed him and protested at the time.

Eiffel is a new software engineering language constructed from
new materials in a new manner. It:

embodies a "certain idea" of software construction: the
belief that it is possible to treat this task as a serious
engineering enterprise, whose goal is to yield quality software
through the careful production and continuous enhancement of
parameterizable, scientifically specified reusable components,
communicating on the basis of clearly defined contracts and
organized in systematic multi-criteria classifications.

Such aims lead a new culture of software development....

Eiffel is nothing else than these principles taken to their
full consequences. [5, vii]

Like Objective-C, Eiffel seeks to change the way we develop
software, rather than just the language we develop it in. Rather
than adding to the old, however, Eiffel takes the bold step of
creating a new language from scratch with many new features that
fully expresses its author Bertrand Meyer's philosophy [5]. The
quick adoption of the language by many companies for large
projects, despite it being a proprietary product of Interactive
Software Engineering until recently, demonstrates the merit of
this approach.

Philosophy The philosophy of Eiffel is to support the
creation of quality software by creating a language (and
libraries, and environment) that encourages the creation of
reusable, extendible, reliable, efficient, open, and portable
software. To this end, "the principles of object-oriented design
provide the best known technical answer." [5, 4]

Eiffel strives to support this philosophy by including specific
language features that encourage program quality, and also
eliminating features that detract from it--even at the cost of
efficiency in coding or execution. The selection of what went in
and stayed out was based on the known base of software
engineering wisdom and research. Features which were known to
cause problems on a theoretical basis (e.g. goto) were rejected,
even if popular; likewise features known to enhance quality (e.g.
garbage collection) were included, even at the cost of run-time
support. The common object-oriented paradigm above was really
only supported because it has been shown to produce good
software. In some cases, Eiffel pioneered features because Meyer
believes they will prove to produce effective software.

In addition to his primary aim, Meyer also sought to make the
language both as highly as expressive as possible and intuitive
as possible. Although these may not directly encourage the
production of high quality software, they do indirectly enhance
it by reducing the complexity of the language, and hence programs
in it, and making it easier to learn, and thus easier to master.
The decision to make the features in Eiffel quite modular also
improve on these characteristics. Although subsets of Eiffel are
not directly supported, the learning of Eiffel via subsets is.

Features The features in Eiffel have been chosen very
systematically to support the elements noted above that Meyer
views as important in contributing to high quality software,
namely reusability, extendibility, reliability, efficiency,
openness, and portability. These, and other notable features
follow.

Reusability Reusability features include those that
inspire class reuse both as client or as child (through
inheritance). These include:

o Generic classes. Classes may be instantiated in terms of
any type (unconstrained generic classes) or a particular class
(constrained generic class). Descendants of the class may be
used freely used so long as they conform to the original class.

o Multiple inheritance. Inherited features may be renamed;
this approach resolves any naming conflicts (such as those
occurring with repeated inheritance). Multiply inherited classes
are only effectively inherited once (like the C++ virtual base
classes); through renaming features of all duplicate classes may
be preserved.

o Operator overloading (including unary, infix, and prefix
operators).

Extendibility Features that make a language extendible are
those facilitate improving existing code as well as maintaining
it, or adapting it to changing environments. Features directly
supporting this are:

o Features in classes are uniformly accessed (object.feature)
regardless of implementation. A function with no parameters is
identical to a read-only variable for all purposes; sub-classes
may override a feature implemented as a variable with a function
and vice-versa. All client code remains unchanged.

o Any inherited features may be redefined (in contrast with
C++ where only virtual functions may be redefined) except ones
that are explicitly frozen by the ancestor class. Redefined
features must conform by type equivalence and assertions to the
original features.

o Arguments to routines may be declared to be like another
variable so that inheriting classes may simply redefine the one
variable and all routines which operate on it are automatically
changed.

o Features may be removed (undefined) as well as added to a
class. Two features with the same final name may be joined into
one.

o Classes are organized into clusters; names must only be
unique within clusters.

Reliability Although other issues take the forefront in
research and public attention, without reliability they are moot.
Reliability is necessary (but not sufficient) and is the
underlying issue behind the "software crisis." Eiffel improves
reliability by supporting:

o Information hiding--all features may be selectively exported
to zero or more classes (including the creation mechanism,
allowing one class to produce instances but all others to use
them). The export status of inherited features may be changed
by any class that inherits them, but all features are available
to descendent classes.

o Full support for assertions, preconditions, post-conditions,
and invariants is provided. Assertion checking may be disabled
to improve performance. The assertion mechanisms in Eiffel are
beyond the scope of this paper, but are worthy of study in
themselves and are a powerful argument for using the language.
Assertions hold when inherited, and so guarantee the behavior of
descendent classes.

o Exception handling is included in Eiffel and is properly
linked within the assertion framework to allow retries and
graceful failure where possible.

o Language features which hinder reliability, including
pointers, global variables, and gotos, are not supported.

o All variables are guaranteed to be initialized to a safe and
valid state at run-time creation.

Efficiency Smalltalk is a wonderful language to develop
in, with its direct support of the object-oriented paradigm,
effective tools, and interpretive nature. But it is too hardware
intensive (but aren't the cost of custom bit-slice processors
coming down?) for use in most environments. Eiffel pushes to
succeed in these environments by allowing:

o A type may be expanded so that all of its data is inline
rather than having references to it.Objects may be expanded
inline

o Although the basic types (BOOLEAN, CHARACTER, INTEGER, etc.)
are classes and instances of them objects, they are treated
specially for maximum performance.

Openness Although Eiffel is a new language with new
features, it must coexist with existing languages if it is to be
useful. Thus Eiffel allows:

o The body of any routine may be implemented in another
language.

Portability The days of MVS and with it the domination of
one architecture are passing. Languages must support different
platforms if they are to be widely used. Eiffel acknowledges
this by including:

o A predefined class, Platform, is included that handles all
language differences between platforms (size of integers, etc.).

Miscellaneous Eiffel includes quite a few novel features
not directly related at improving program quality in the above
sense. These include:

o Full support of persistence through a common class designed
to be inherited by everything. Objects rolled-in may be retyped
using a "reverse assignment attempt" that types a retrieved
object according as that of an expected type, if the object
conforms to that type, or void if not. Objects may also
automatically be stored on certain conditions. With the use of
two statements, when a program ends all of its data can be
automatically stored, and when it re-executes all its data can be
retrieved.

o An indexing clause for references to other works. This
section is ignored by the compiler but useful for comments and
can be processed to build indexes of systems. Meyer gives an
example in [5] for a Document class:

indexing
text, text_processing, TeX;
author: "Tatiana Sergeevna Krasnojivotnaya";
approved_by: "Giovanni Giacomo della
Gambagialla";
original: 21, March, 1988;
last: 12, July, 1992
class DOCUMENT inherit
...

o An obsolete clause indicating that this class is not up to
current standards.

o Multiple feature clauses which allow features to be
separated into groups and commented. These can be machine read
since the format is part of the language.

o Features may have synonyms.

o A routine (procedure or function) may have either a once
clause or a do clause. The once clause is executed one time; the
result of its execution is returned (if the routine is a
function) as the result of every succeeding call (i.e. an
initialization routine or computed constant). Do clauses are
executed repeated as per routines in other languages.

These demonstrate the depth of Meyer's commitment to improving
the overall programming environment (including programming tools)
and how it influenced his language design.

Consequences

Since the early days of OS/360 we have come to learn that nothing
exists in a vacuum; every design decision, no matter how small,
impacts everything else in a design. Software engineering is in
one sense a systematic attempt to reduce the repercussion of
design changes on a system. Object-orientation is a specific
methodology for accomplishing this end. By defining classes of
objects with tightly restricted interfaces, we hope to reduce the
side effects of change so long as we can define interfaces that
are sufficiently robust to survive intact.

In each of the above three languages, the designers were faced
with the common problem above--mitigating change--as well as a
host of others. In this section, for each language, I analyzed
the consequences of the designers' philosophies and design
decisions looking for impact on the various phases of program
development: analysis/design, coding, testing, and modification.
I paid particular attention to effects on reusability,
reliability, maintainability, and robustness considering a
variety of program sizes. Rather than looking at each philosophy
for notable products, I used a synthetic approach of identifying
obvious results when the results of certain philosophies were
well known, testing the languages against classical problems,
comparing the languages with their peers, and using war stories
gathered from actual language users. The results follow.

C++

C++ is driven by a philosophy of simplicity, compatibility, and
efficiency that, while necessary for a low-level language proves
quite restrictive in other contexts. All the phases of program
development demonstrate this, from design to maintenance:

Design/Analysis o If two users are working in tandem
on a project, one writing a new class and the other using it,
both the interface and implementation of the class must be fully
mapped out before any simultaneous development can begin. C++
does not allow the implementation of a class to change without
forcing re-compiles of all client classes even if the change does
not affect any client classes. Consider:

/* supplier.c -- this also must be in supplier.h */
class problem {
public:
int foo;
private:
int okaySoFar;
}

/* client.c */
#include supplier.h

static problem p;
p.foo=1;

If the supplier changes the okaySoFar to a character instead
of an integer, the header file must be changed to reflect it and
every client file must be re-compiled even though the change did
not actually affect any of them from the client's perspective.

o The lack of unconstrained generic class containers
(templates) prevents fully reusable heterogenous containers from
being developed. With the strong typing in C++ each container
can only operate on objects of types which are known for which
the containers have been declared at compile time. This
discourages use of iterative design models and requires design
decisions regarding the classes that can be used and their
relation to one another to be made very early in the design
cycle.

o C++ restricts dynamic binding to virtual functions and only
allows it to distinguish which function to invoke for an object
belonging to a class or derived class where it cannot determine
at compile-time which of the routines to invoke. The set of
routines must be defined at compile time which prevents true
dynamic binding to an object whose class information is not
available until run-time. Again, this forces detailed decisions
early in the design cycle and makes the resulting code brittle.

Coding o C++ fully supports multiple inheritance, but
the resolution of conflicts between names in base classes
requires a thorough understanding of the resolution precedence
scheme and can be confusing. The method of resolving them, by
prefixing them with their class name, works fine. The example
below illustrates C++'s functional but problematic approach:

class BaseballBase {
public:
int plate;
status cleanliness;
};

class Mound {
public:
int height;
status cleanliness;
};

class PitchersMound: public BaseballBase, public Mound
{};

int PitcherSlips(PitchersMound *p) {
if (cleanliness = bad) // must be
Mound::cleanliness or
// BaseballBase::cleanliness unless
// Mound has BaseballBase as a
(virtual)
// base
return TRUE;
else
return FALSE;
}

o The decision to make all class and public member names
global creates unnecessary opportunities for name conflicts.

o Abstract classes cannot be used as arguments. If a member
can only operate on a whole hierarchy of classes whose base
classes are have pure virtual functions, new classes may need to
be created to resolve the problem.

o Although operators can be overloaded to allow new classes to
mimic old classes' behavior, there are many potential problems.
There is no protection against defining conflicting operations
based on identical operators (e.g. making the + operator
subtract). The rules for type conversion and promotion of
arguments are as complicated as PL/I: conversions are based on
implicit type rules as well as the type of each argument in
user-provided overloaded routines requiring detailed knowledge of
the C++ argument matching rules (14 pages in [6]) for reliable
code. The resolution of user-defined types is left ambiguous.

o Class variables are supported through global variables and
static file variables. (Static file variables are variables that
are global to all classes within a source file but hidden from
all classes outside.) The former fail to isolate
private-but-shared data; The latter are unrealistic for large
programs and programs that share subsets of variables (A and B
share C's variables but A does not have access to B's.)

o Many of the responders to my survey on Usenet viewed C++ as
a difficult language to learn and work with. The large number of
language features, without the presence of orthogonality or some
other means of reducing complexity, made learning and coding in
C++ problematic. [8]

Maintenance o While the type checking system of C++
correctly recognizes that type restrictions are an essential part
of creating reliable systems (a PencilJar class should not allow
Elephants and RemoteMountedFileSystems to be placed in it), it
requires substantial source changes every time a new hierarchy of
classes (e.g. an external library) is added if the new classes
are to be mixed with the old. Multiple inheritance makes this
feasible, but at a high cost in programmer time.

Some respondents to my survey mentioned the lack of run-time
type-checking as a serious problem as well.

o The lack of garbage collection effectively restricts C++
from being used to write application programs, which create and
dispose of substantial numbers of objects at run time, and
programs where reliability is paramount. Without garbage
collection there is no way to guarantee that there are no
dangling pointers present with the resultant debugging
difficulties.

This was the single largest complaint among all the users of
C++ who responded to my requests for personal experiences with
these languages. Garbage collection must be included for
applications development--but it cannot be present for low-level
systems programming. In my mind, the problem is with people's
poor choice in using C++ for applications programming (based on
the false premise that it is a general-purpose object-oriented
language). [8]

o Although access control provides more levels of information
hiding than most system, it does not offer any restriction on the
rights granted to visible members. Any class with access to a
member variable may change that at will.

In addition to the problems with program development produced by
C++, the following miscellaneous issues were noted:

o Without a uniform representation for objects, implementing
generics (templates) is very difficult. Instantiating a template
of integers creates significantly different code than
instantiating a template of ClassesWithALotOfInstanceVariables,
for example. A compiler supporting templates must support this
by requiring access to the source code and thus preventing
commercial libraries that are shipped object-code-only from using
templates, or by maintaining pre-compiled images of templates.
Either way, this greatly increases the complexity of a compiler.
Borland's latest C++ compiler still does not support generics and
most implementations contain bugs.

o The great emphasis placed on performance yields a trade-off
with language and compiler complexity. The language is uniformly
viewed as difficult to learn and use and compiler support for the
language is lagging more than any other language since Ada.

Although there are some problems in C++ that make programming
unnecessarily difficult, such as its treatment of abstract
classes, most of the problems are so severe as to prevent
development of certain types of systems using C++. Without major
enhancements, C++ simply cannot be used to develop classes that
are easily reusable across systems, highly reliable software, or
large partially unspecified projects involving several
programmers.

Objective-C

The philosophy of Objective-C has produced a small, easy to
understand, and imminently useful language. Cox judged wisely
when he included dynamic-binding; it is a powerful tool that
considerably simplifies programming and encourages reusability
(although it is not sufficient for it). But his single-minded
pursuit of his vision of "software-ICs," and his implementation,
leaves some key issues unresolved that affect development with
Objective-C throughout the development cycle:

Analysis/Design o Single inheritance requires
substantial duplication of code for classes using shared
features, in direct conflict to Objective-C's goal. The
specification problem that Cox refers to can be solved through
the use of constructs guaranteeing the specification of inherited
methods such as those found in Eiffel, but the underlying
language must support multiple inheritance.

o The lack of overloading yields class interfaces that are not
uniform, both increasing the overall complexity of any project
and forcing programmers to learn (potentially) a new interface
per new class.

o Objective-C supports true heterogenous containers with
variable sizes allowing the most flexible data structures
possible (but see the maintenance issues below for the
disadvantage).

Coding o With static and dynamic binding present,
Objective-C allows the creation of fast code while maintaining
the overall generality for flexible system development. It
properly allows the client code to make the decision as to how it
is bound to a supplier. Only an automatic binding system such as
the dynamic compiling in SELF which converts itself into a
statically bound system as possible would be better. [2]

o Class variables ("factory variables" in Cox's lexicon) are
implemented as global variables with the same problems as noted
in C++ above.

o Only methods are publicly accessible, requiring the creation
of many trivial functions that return a value with the incumbent
overhead.

o While supporting the syntax and much of the functionality of
Smalltalk, the lack of an interpretive environment discourages
the experimental programming approach that yields much of
Smalltalk's productivity. The author's implementations (from
StepStone) of the compiler is actually a preprocessor for C
compilers, which slows down the development cycle even further,
encouraging a batch mentality.

o A header file must be created for each class, but since the
header only contains the public interface to the class its
creation is only a minor nuisance. Cox gives a program to create
such files automatically from the class source code.

Maintenance o The all-or-nothing nature of type
checking in Objective-C (all static or all dynamic) guarantees
the creation of unreliable software since unsupported messages
may be sent to classes at run-time. Cox failed to see that this
is a specification requirement rather than a performance
requirement; static typing is as essential for dynamically bound
portions of programs as well as statically bound.

o The lack of garbage collection in Objective-C prevents
reliable programs from being developed. Object pointers are
unlike ordinary pointers; they are typically passed around in a
variety of contexts. Where a misplaced buffer pointer might
result in data errors within an application, a misplaced object
pointer usually results in a fatal and abrupt application ending.

o The combination of C and Smalltalk provides a combination
that is very easy to learn for programmers with experience in
either or both languages and very easy to work with. Complexity
is well managed, and the resulting Objective-C programs are
inevitably easier to read and understand, with the attending
results in maintainability. An example below of a Graph class
which was used in a program to record the dependency
relationships of C functions (who uses what) illustrates this:

// Dependency Graph
#import "objc.h"
#import "Graph.h"
#import "Node.h"

/* define the implementation of class Graph which
inherits from Set */
@implementation Graph:Set {}

/* instance methods begin with -, class with +; STR is
a string */

// Add a new node
- addNode:(STR) aCharPointer
{ return [2 filter:[Node str:aCharPointer]]; }

// Add a new node and define it
- defineNode:(STR)aCharPointer
{ return [[2 addNode:aCharPointer] define]; }

// Mark the given node (and all nodes referenced by it)
- mark:(STR)aNodeName
{ return [[2 addNode:aNodeName] mark]; }
@end

Eiffel

With his wide assessment of the programming crisis and the best
understanding of the impact of various object-oriented features,
Meyer has managed to sidestep most of the major problems with
other object-oriented languages. But nothing man makes is
perfect, and Eiffel is no exception. Specific problems include:

Analysis/Design o The decision to limit class
information to compile time prevents the development of a whole
class of systems programs, such as browsers, linkers, and
librarians. While Eiffel promotes ease of change at the
compile-time level, it hinders it at the run-time level.

This is an inherent error in the language design of Eiffel,
not necessarily a philosophical problem. Unfortunately, it
cannot be easily worked around without effectively simulating a
correction in the language.

o The typing system, with its master type of All, allows for
the creation of heterogenous containers while preserving type
information for creating homogenous containers as well.

o The combination of strong typing and dynamic binding allows
yields reliable code (operations are guaranteed to be handled by
the objects on which they are operated) with the benefits of
dynamic binding (but note how the lack of class information at
run-time hinders this).

o The complete lack of support for static binding (Meyer calls
it a "crime" to use static binding in object-oriented software)
prevents Eiffel from being used to write systems or time-critical
software. Since many of the large projects for which Eiffel is
designed contain time-critical portions, a designer is forced to
use more than one language for his project.

Coding o No support for shared variables is included.
None is needed so long as the variables are included in classes
that export the variables to the classes that need them.

o Free routines, those separate from a class, do not exist in
Eiffel but class support can effectively take care of them.

o Eiffel contains only limited support (through manifest
constants) for non-ASCII characters. No effort to support wide
(double-byte character sets) characters is included, nor can some
be easily added.

o Eiffel is a large language. It is very well engineered,
which reduces the language complexity to make learning and using
it reasonably easy, but it does not reduce its size or bulk.
This makes writing compilers for Eiffel is a difficult task,
simply because supporting the features of Eiffel is difficult.

o The style of Eiffel (Meyer specifies one down to which fonts
to use for what part of language grammar) shares ancestry with
both Ada and Cobol in being ugly and long-winded. There is no
attempt to reduce language features to one character as in C++;
rather moderate sized programs quickly bloom to consume reams of
paper. This slows down coding, but the results may merit all the
verbiage. The following sample taken from [5, 6-10] demonstrates
this:

class ACCOUNT creation
make
feature
balance: INTEGER;

owner: PERSON;

minimum_balance: INTEGER is 1000;

deposit(sum: INTEGER) is
require
sum >= 0;
do
add(sum);
ensure
balance := old balance + sum
end; -- deposit

Maintenance o The inclusion of garbage collection
allows for the creation of reliable programs. Eiffel's reliance
on it for all storage management, however, precludes the use of
Eiffel in storage-constrained or time-critical applications, such
as real-time programming.

o There is no way to prevent other classes from gaining full
access to all features in a class since all features are
automatically available to any descendent of a class. This lack
of protection (available in C++) weakens information hiding and
reduces the reliability of the overall system.

o Eiffel lacks the platform-independent persistent data format
of Objective-C. When the format of a class changes (i.e.
instance variables added or deleted) new code must be written to
convert any existing objects from the old format to the new.
This platform-specific format also makes the software much less
resilient to changes in hardware than it should be.

Results and Discussion

C++ has proven itself to be an excellent replacement for C for
systems programming, meeting its authors goals handily. As a
direct consequence of this focus on low-level/low-overhead
programming, however, C++ has shown itself to be inferior to
both Objective-C and Eiffel for application programming and
especially for large systems development. C++ cannot be used to
write dynamic programs which require run-time typing, and its
lack of garbage collection limits its application to very
tightly defined and controlled applications. In summary, it is a
better C, but nothing more.

In contrast, Objective-C excels at programs requiring run-time
typing and the full message support of Smalltalk. It also is an
excellent tool for systems programming, but also suffers from a
lack of garbage collection. While it is the closest to being a
general-purpose object-oriented language, having demonstrated its
use in applications and systems on the NeXT, it fails to
revolutionize the world because it fails to truly advance the
level of compiled languages.

Eiffel, however, is truly revolutionary in that it provides all
the tools for specifying and building reusable systems. It does
not aim to be a systems programming language, it is not, and it
cannot be one. But given the caveats regarding the bulk of the
language and the performance related issues (which should improve
as compilers do), Eiffel is the best choice of the three for
large programming projects or mission-critical software. Conclusion

As is the case in real life, philosophies often preclude
compromise. In each of the languages surveyed some of the
designer's decisions involved burning bridges--making decisions
that inherently limited the power of the language rather than
expanding it:

o The decision in C++ to make a language useful for systems
programmer inherently prevents it from addressing issues
important to applications programming and large systems
development. Large systems, where reliability issues are
critical, will continue to require advanced features and tools
with far too much overhead for systems development.

o The decision in Objective-C to focus on getting dynamic
binding into a language as quickly as possible rather than
considering all the other issues involved has resulted in an
imminently useful language now, but one that ultimately does not
contribute to ending the software crisis.

o The decision in Eiffel to include all the bells and whistles
needed for large applications programming add too much overhead
for small projects and constrain the performance of large ones.

These problems are severe enough to prevent any of these from
serving as a truly general purpose language. If the languages
are broken into two groups, however, systems-oriented versus
application-oriented, Objective-C++ seems the answer for the
former. Given that the natural implementation for Objective-C is
a preprocessor for C (as is the AT&T cfront implementation of
C++) the two languages could be combined. Although I have not
tested this, the language definitions do not seem to contain any
conflicts between the two languages; the only conflicts that
would arise would be in library names and run-time support
(initialization).

Eiffel is clearly superior to even this combination for large
applications develoment.
References

[1] G. Booch. Object Oriented Design with Applications.
Benjamin/Cummings, 1991.

[2] C. Chambers and D. Ungar. "Making Pure Object-Oriented
Languages Practical", OOPSLA '91: Conference on Object-Oriented
Programming Systems, Languages, and Applications, November, 1991,
1-15.

[3] B. J. Cox and A. J. Novobilski. Object-Oriented
Programming: An Evolutionary Approach, 2d ed. Addison-Wesley,
1991.

[4] A. Goldberg and D. Robson. Smalltalk-80: The Language and
Its Implementation. Addison-Wesley, 1983.

[5] B. Meyer. Eiffel: The Language. Prentice Hall, 1992.

[6] B. Stroustrup and M. A. Ellis. The Annotated C++ Reference
Manual, corr. ed. Addison-Wesley, 1991.

[7] B. Stroustrup. The C++ Programming Language, 2d ed.
Addison-Wesley, 1991.

[8] Usenet. Responses to "* Wanted: war stories *" in
comp.lang.c++, comp.lang.eiffel, and comp.lang.objective-c.
1993.

Ernest Prabhakar

unread,
May 10, 1993, 2:53:54 PM5/10/93
to
In article <93128.0257...@UCF1VM.BITNET> Mark Woodruff
<CDA9...@UCF1VM.BITNET> writes:
> This is the report based, in part, to the request I made earlier
> for war stories related to C++, Ojb-C, and Eiffel.

Mark,

Excellent summary, thanks. A couple questions about your analysis of ObjC:

> Objective-C: Stepstone Toward a New Era

.. [munch]...

There seem to be three key (and quite valid) complaints:
Single Inheritance, Class variables, and garbage collection.

> Analysis/Design
> o Single inheritance requires
> substantial duplication of code for classes using shared
> features, in direct conflict to Objective-C's goal. The
> specification problem that Cox refers to can be solved through
> the use of constructs guaranteeing the specification of inherited
> methods such as those found in Eiffel, but the underlying
> language must support multiple inheritance.
>
> o The lack of overloading yields class interfaces that are not
> uniform, both increasing the overall complexity of any project
> and forcing programmers to learn (potentially) a new interface
> per new class.

The first issue, Single Inheritance (and the related issue of
overloading) seem to have been at least partly addressed by NeXT
by the introductin of Protocols. Have you looked at those, and if so,
how much do you think they help?

> o Class variables ("factory variables" in Cox's lexicon) are
> implemented as global variables with the same problems as noted
> in C++ above.
>

> o The lack of garbage collection in Objective-C prevents
> reliable programs from being developed. Object pointers are
> unlike ordinary pointers; they are typically passed around in a
> variety of contexts. Where a misplaced buffer pointer might
> result in data errors within an application, a misplaced object
> pointer usually results in a fatal and abrupt application ending.

There is some discussion in the GNU ObjC list about adding both these
features. They seem useful and in keeping with the philosophy of the
language, but there's still some confusion about how exactly to implement
them. Do you have any thoughts about that?

>
> These problems are severe enough to prevent any of these from
> serving as a truly general purpose language. If the languages
> are broken into two groups, however, systems-oriented versus
> application-oriented, Objective-C++ seems the answer for the
> former. Given that the natural implementation for Objective-C is
> a preprocessor for C (as is the AT&T cfront implementation of
> C++) the two languages could be combined. Although I have not
> tested this, the language definitions do not seem to contain any
> conflicts between the two languages; the only conflicts that
> would arise would be in library names and run-time support
> (initialization).

Well, actually I've been using Objective-C++ for over two years
(on the NeXT), and I can assure it is very nice. It of course still
falls short in areas where both are weak, like GC.

Thanks again,

- Ernie P.

--
Ernest N. Prabhakar Caltech High Energy Physics
Member, League for Programming Freedom (lea...@prep.ai.mit.edu)
CaJUN President NeXTMail:ern...@pundit.cithep.caltech.edu
"...and ourselves, your servants for Jesus sake." - II Cor 4:5b
#import <std/disclaimer.h>

Jon Rosen

unread,
May 10, 1993, 7:11:03 PM5/10/93
to
In article <1sm8c3...@gap.caltech.edu> ern...@pundit.cithep.caltech.edu (Ernest Prabhakar) writes:
>In article <93128.0257...@UCF1VM.BITNET> Mark Woodruff
><CDA9...@UCF1VM.BITNET> writes:
>> This is the report based, in part, to the request I made earlier
>> for war stories related to C++, Ojb-C, and Eiffel.
>
>Mark,
>
>Excellent summary, thanks. A couple questions about your analysis of ObjC:

Agreed.


>
>> Objective-C: Stepstone Toward a New Era
>.. [munch]...
>
>There seem to be three key (and quite valid) complaints:
>Single Inheritance, Class variables, and garbage collection.
>
>> Analysis/Design
>> o Single inheritance requires
>> substantial duplication of code for classes using shared
>> features, in direct conflict to Objective-C's goal. The
>> specification problem that Cox refers to can be solved through
>> the use of constructs guaranteeing the specification of inherited
>> methods such as those found in Eiffel, but the underlying
>> language must support multiple inheritance.
>>
>> o The lack of overloading yields class interfaces that are not
>> uniform, both increasing the overall complexity of any project
>> and forcing programmers to learn (potentially) a new interface
>> per new class.
>
>The first issue, Single Inheritance (and the related issue of
>overloading) seem to have been at least partly addressed by NeXT
>by the introductin of Protocols. Have you looked at those, and if so,
>how much do you think they help?

Protocols only addresses the method/message part of the problem. There
is no concept of multiple inheritance for the data part of the problem.
That is, if conforming to a protocol requires some common set of
instance variable data, those instance variables must be defined in
each class. The multiple inheritance approach would permit definition
of a class which inherits both data AND methods from several higher
level classes. Also, protocols simply provide a template (although
that word is used in a different context for C++ so don't get them
confused) for conforming to a set of messages. There is no way to
inherit the behavior from a predefined protocol (which makes sense
since the protocol definition has no way of knowing what instance
data to act on, or even if there is any instance data). Protocols
are a wonderful means of assuring conformance to a required set of
messages but that is about all it really does.

BTW, I also wish Objective-C permitted argument overloading and
operator overloading. Overloading can only be accomplished in
terms of the classes themselves (different classes can overload
the same syntactical message) but does not permit differentiation
based on argument types. Thus, C++ permits the Objective-C
equivalent of:

- someMethod:(int) x;
- someMethod:(float) x;
- someMethod:x; /* takes an id */

whereas Objective-C requires that you do something like:

- someIntMethod:(int) x;
- someFloatMethod:(float) x;
- someObjectMethod:x;

which is cumbersome. Of course, one of the problems inherent in Obj-C
is that the native C datatypes are not objects and therefore don't
carry around their own identity. Thus, to figure out which "someMethod"
to call in the above case either requires compile-time binding based
on the knowledge of the argument types or some very different parameter
passing mechanism for native C datatypes which would permit true Obj-C
run-time binding. Practically, since native C datatypes can't mutate
their own identities (like objects can), compile-time bindings, at least
with respect to the native C datatype arguments, would probably suffice.


>
>> o Class variables ("factory variables" in Cox's lexicon) are
>> implemented as global variables with the same problems as noted
>> in C++ above.
>>
>> o The lack of garbage collection in Objective-C prevents
>> reliable programs from being developed. Object pointers are
>> unlike ordinary pointers; they are typically passed around in a
>> variety of contexts. Where a misplaced buffer pointer might
>> result in data errors within an application, a misplaced object
>> pointer usually results in a fatal and abrupt application ending.
>
>There is some discussion in the GNU ObjC list about adding both these
>features. They seem useful and in keeping with the philosophy of the
>language, but there's still some confusion about how exactly to implement
>them. Do you have any thoughts about that?

Class variables should be pretty easy. It has been recommended elsewhere
on the net that a relatively consistent syntax for class variables would
look like:

@interface MyObject:Object

+{
int x;
float y;
}
-{
int a;
float b;
}

where the +{..} would enclose class variables and the -{..} would
enclose instance variables. We could keep {..} without a - or +
the same as it is for backwards compatibility (that is, it is the
same as -{..}).

I kind of like that approach. Its simple and doesn't really require
that one learn anything new.

Garbage collection is MUCH MUCH tougher in Obj-C or C++. Since C
permits you to create and use pointers to data without going through
an objective access layer, there is no way to make sure that any
objects that are created aren't captured by reference somewhere in
the program without the object layer knowing about it. Arbitrarily
imposing garbage collection would break TONS of existing code.

The only reasonable solution would be to add a new garbage collection/
reference count facility to the class definition which would impose
a GC/RC protocol on the class and require that the user not do anything
"illegal". Of course, this level of trust can not be enforced by thge
compiler or run-time system in any reasonable way. Thus, the chance
for major failure is still present.

Jon Rosen

Dag Bruck

unread,
May 11, 1993, 5:49:55 AM5/11/93
to
In <comp.lang.c++,comp.lang.objective-c,comp.lang.eiffel> Mark Woodruff <CDA9...@UCF1VM.BITNET> writes:
>
> C++

>
> Design/Analysis o If two users are working in tandem
>on a project, one writing a new class and the other using it,
>both the interface and implementation of the class must be fully
>mapped out before any simultaneous development can begin.

There are two useful approaches:

1. Use an abstract base class. If A writes the abstract base class
first and the derived classes with the implementation later. If user
B only uses the interface provided by the abstract base class, the
implementation can re redefined with problems for B.

2. Use a pointer to the concrete representation. The contents of the
representation need not be known by B if the interface class provides
access routines.

>o The decision to make all class and public member names
>global creates unnecessary opportunities for name conflicts.

Member names are not global.

>o Abstract classes cannot be used as arguments.

You can, but the argument must be a reference or a pointer to the
abstract class.

>... The resolution of user-defined types is left ambiguous.

Is it? Please explain further. An example would be useful.

>o Class variables are supported through global variables and
>static file variables.

There are proper class variables:

class Foo {
private:
static int this_is_a_class_variable;
};

>o The lack of garbage collection effectively restricts C++
>from being used to write application programs, which create and
>dispose of substantial numbers of objects at run time, and
>programs where reliability is paramount. Without garbage
>collection there is no way to guarantee that there are no
>dangling pointers present with the resultant debugging
>difficulties.
>
> This was the single largest complaint among all the users of
>C++ who responded to my requests for personal experiences with
>these languages. Garbage collection must be included for
>applications development--but it cannot be present for low-level
>systems programming. In my mind, the problem is with people's
>poor choice in using C++ for applications programming (based on
>the false premise that it is a general-purpose object-oriented
>language). [8]

I think you should work some more on the discussion of GC. Your
claims are not well supported, and imply that all applications have
identical needs.

>Although there are some problems in C++ that make programming
>unnecessarily difficult, such as its treatment of abstract
>classes, most of the problems are so severe as to prevent
>development of certain types of systems using C++. Without major
>enhancements, C++ simply cannot be used to develop classes that
>are easily reusable across systems, highly reliable software, or
>large partially unspecified projects involving several
>programmers.

I hope the errors I have pointed out will give you reason to
reconsider your conclusions. I think this last paragraph is
insufficiently motivated. Maybe you should give som concrete examples
of types of systems that cannot (or only with great difficulty can) be
developed using C++.


-- Dag Bruck

Peter Juhl

unread,
May 11, 1993, 9:36:44 AM5/11/93
to


In addition to the points Dag Bruck made about the War Stories
I will add

> Either way, this greatly increases the complexity of a compiler.
> Borland's latest C++ compiler still does not support generics and

Borland C++ has supported templates for the last year or so

> His goal is not an environment for
> hundred-million line generational projects but to enable "a
> single person to cope with 25,000 lines of code." [7, 7]

the actual qoute is

" .. so that it would not be unreasonable for a single person
to cope with 25,000 lines of code. C++ has by now shown to
over-fulfill that goal.
Much larger programs exist, but the ones that work generally
turn out to consist of many nearly independent parts, each one
well below the limits previously mentioned."


--- peter (pe...@research.att.com)

I Stephenson

unread,
May 11, 1993, 4:55:36 AM5/11/93
to
In article <1sm8c3...@gap.caltech.edu> ern...@pundit.cithep.caltech.edu
(Ernest Prabhakar) writes:
> In article <93128.0257...@UCF1VM.BITNET> Mark Woodruff
> <CDA9...@UCF1VM.BITNET> writes:
> > o Class variables ("factory variables" in Cox's lexicon) are
> > implemented as global variables with the same problems as noted
> > in C++ above.
> >
> > o The lack of garbage collection in Objective-C prevents
> > reliable programs from being developed. Object pointers are
> > unlike ordinary pointers; they are typically passed around in a
> > variety of contexts. Where a misplaced buffer pointer might
> > result in data errors within an application, a misplaced object
> > pointer usually results in a fatal and abrupt application ending.
>
> There is some discussion in the GNU ObjC list about adding both these
> features. They seem useful and in keeping with the philosophy of the
> language, but there's still some confusion about how exactly to implement
> them. Do you have any thoughts about that?
>

Am I the only one who see's the GNU ObjC list as rushing headlong towards
extending ObjC into oblivion? (Aside from the otherwise _excellent_work
they're doing in bringing this great language to the masses).

The great thing about ObjC is that it DOESNT try and fix C (as C++ does).
It's an absolute minimal extension. There are already a number of features
floating around that extend the original Cox model, many in a usefull way,
but the last thing that needs to be done is a whole host of extra widdly bits
(ala GCC which goes far beyond ANSI, let alone K&R). Take it slow, and PROVE
that the new features are NECESSARY.

On the specific issues above, Class variables may be a good idea (though to
be picky they're generaly static rather than global, so there is some
encapsulation afforded - only inheritance is a real problem).

Garbage collection I say a big NO! Leaving an unreferenced object around is
BAD PROGRAMMING. Object pointers ARE like ordinary pointers - if you (m)alloc
something you should be responsible for it, and free it when its finished
with (didn't your mother always tell you to put your toys away when you'd
finished with them?).

The distinction made (that bad mallocs creatre wrong results, while bad
alloc's crash programs) is hardly a good reason for garbage collection
objects - A crash is almost invariably better than a wrong result. At least
you're sure something is wrong.

GC works in SmallTalk, but I thing most C programmers would not trust it. I'd
much rather explicitly free the object than just leave it around in the hope
it gets found later (it's also much harder in ObjcC than ST, as in OC not
everything is an object).

I also feel simlarly about messages to nil, but I guess I'm stuck with that
one :-)

Ian Stephenson
University of York

Gordie Freedman

unread,
May 11, 1993, 3:03:26 PM5/11/93
to
In article <1993May11....@ohm.york.ac.uk> i...@ohm.york.ac.uk (I
Stephenson) writes:
>
> The great thing about ObjC is that it DOESNT try and fix C (as C++ does).
> It's an absolute minimal extension. There are already a number of features
> floating around that extend the original Cox model, many in a usefull way,
> but the last thing that needs to be done is a whole host of extra widdly bits
> (ala GCC which goes far beyond ANSI, let alone K&R). Take it slow, and PROVE
> that the new features are NECESSARY.
>

I think this is a very good point - rather than bringing in all the king's
horses, it would probably be good to "field test" major changes to the language
over time, implementing few new changes at a time. I didn't get the impression
that Gnu was going to massively overhaul the language though.

>
> Garbage collection I say a big NO! Leaving an unreferenced object around is
> BAD PROGRAMMING. Object pointers ARE like ordinary pointers - if you (m)alloc
> something you should be responsible for it, and free it when its finished
> with (didn't your mother always tell you to put your toys away when you'd
> finished with them?).
>

> Ian Stephenson
> University of York

Having worked with both Objective-C (and C++ for that matter), and languages
with garbage collection, I have to disagree. The advantages to garbage
collection are:

1) You don't have to worry about whether or not to free data returned from
vendor's libraries (Motif, for example - do you free all those XmStrings it
returns to you? Sometimes - and this was not always documented in the good old
days - besides I believe that Motif itself, an industrial strength framework
had it's share of leaks). You either cost extra time documenting what to free
and not free (some things can't be freed that are returned from library code),
or you don't get the documentation and you're hosed.

2) You will make less mistakes. Whatever your mother told you about your toys
she is not here to debug your programs. As Purify and other similar products
have shown, just about everybody's an offender when it comes to referencing
freed storage, and to never freeing things.

3) Some things are just plain impossible without GC. If you have something
dynamically created that you need to free (say it is no longer needed in the
mainline code that created it and you need space), but other parts of the
system may have references to it (callback lists, other vendor's code, etc.),
how can you safely free it? You either have to do some strange dance to track
it's usage (which is error prone and time consuming anyway, and which may not
be possible when you didn't write other parts of the program), or you can't
free the storage in situations like that.

4) Convenience and development time. If you are constantly tracking down memory
leaks, and/or coding for checks to see if you should free something or not, you
will not be as productive. I was writing code without GC, and once we got GC
working and I started using it I was spending much less time on many of the
trivial things you have to do without it.

That said, I do not think that GC should be mandatory in a language like
Objective C. There are also reasons not to put it in a language, such as
overhead, difficulty in implementing an smp/thread-safe collector, marking
conservatively when the language can cast pointers to ints and back again, etc.

However, I would be glad to see an optional gc mechanism that you could use in
Objective C. For most of the programs I write I would use it, and if it worked
with multithreaded (or multiprocessing) programs, I'd probably always use it.
--
>>> Gordie Freedman -> gor...@cyclesoft.com NeXTMail Yes!
>>> Thou shalt not inline functions more complicated than 20

Daniel LaLiberte

unread,
May 11, 1993, 3:45:07 PM5/11/93
to
In article <1993May11....@ohm.york.ac.uk> i...@ohm.york.ac.uk (I Stephenson) writes:

Garbage collection I say a big NO! Leaving an unreferenced object around is
BAD PROGRAMMING. Object pointers ARE like ordinary pointers - if
you (m)alloc something you should be responsible for it, and free
it when its finished with (didn't your mother always tell you to
put your toys away when you'd finished with them?).

The problem is that even if you (a module within a larger program) are
finished playing with your toys, someone else (some other module)
might still be using it. So forgetting your reference to an object
may not be bad programming if it is simply the only way to do program
it. I wish people would stop using this "bad programming" argument
against the need for automatic garbage collection because it is simply
wrong in the general case. Granted, simple applications can get by
with manual garbage collection, but that is not to say that they should.

GC works in SmallTalk, but I thing most C programmers would not
trust it. I'd much rather explicitly free the object than just
leave it around in the hope it gets found later (it's also much
harder in ObjcC than ST, as in OC not everything is an object).

What's to trust? If an object doesnt get found by GC, it can only
increase your memory usage for awhile, rather than crash.
Furthermore, I'd rather trust one good programmer to write a
conservative automatic garbage collector than either myself or any
random programmer to write a manual garbage collector that may not
even be able to do the job right.

And reference counting is not sufficient in general.

Dan LaLiberte
lib...@cs.uiuc.edu
(Join the League for Programming Freedom: l...@uunet.uu.net)

Ruediger Bess

unread,
May 11, 1993, 7:04:14 AM5/11/93
to
d...@control.lth.se (Dag Bruck) writes:

>In <comp.lang.c++,comp.lang.objective-c,comp.lang.eiffel> Mark Woodruff <CDA9...@UCF1VM.BITNET> writes:

>>o The lack of garbage collection effectively restricts C++
>>from being used to write application programs, which create and
>>dispose of substantial numbers of objects at run time, and
>>programs where reliability is paramount. Without garbage
>>collection there is no way to guarantee that there are no
>>dangling pointers present with the resultant debugging
>>difficulties.
>>
>> This was the single largest complaint among all the users of
>>C++ who responded to my requests for personal experiences with
>>these languages. Garbage collection must be included for
>>applications development--but it cannot be present for low-level
>>systems programming. In my mind, the problem is with people's
>>poor choice in using C++ for applications programming (based on
>>the false premise that it is a general-purpose object-oriented
>>language). [8]

>I think you should work some more on the discussion of GC. Your
>claims are not well supported, and imply that all applications have
>identical needs.

I think to fix up dangling references dynamically will cause a
big overhead in execution time.
At every access to a pointer you have to test its validity.
Is there any cheap possibility (in developing, in execution time and in
the amount of storage for the memory management ) to fix
this problem ?

Dave Griffiths

unread,
May 11, 1993, 2:54:04 PM5/11/93
to
In article <1993May11....@ohm.york.ac.uk> i...@ohm.york.ac.uk writes:
[...]

>Am I the only one who see's the GNU ObjC list as rushing headlong towards
>extending ObjC into oblivion? (Aside from the otherwise _excellent_work
>they're doing in bringing this great language to the masses).
>
>The great thing about ObjC is that it DOESNT try and fix C (as C++ does).
>It's an absolute minimal extension. There are already a number of features
>floating around that extend the original Cox model, many in a usefull way,
>but the last thing that needs to be done is a whole host of extra widdly bits
>(ala GCC which goes far beyond ANSI, let alone K&R). Take it slow, and PROVE
>that the new features are NECESSARY.
>
>On the specific issues above, Class variables may be a good idea (though to
>be picky they're generaly static rather than global, so there is some
>encapsulation afforded - only inheritance is a real problem).
>

The more I think about it, the clearer the case becomes for adding class
variables. A class is just an object like any other. It's a factory object.
What better place to maintain Lists and HashTables of all the objects you've
instantiated? I see no reason why a class shouldn't maintain state
information. Someone earlier maintained that there was a philosophical
problem with archiving. I disagree. The natural place to archive a collection
of objects of class Foo is in Foo's +write factory method. There are also
some objects that don't naturally have multiple copies. The "Controller"
objects in the NeXT examples for instance. These are good candidates for true
class objects with their own factory variables.

>Garbage collection I say a big NO! Leaving an unreferenced object around is
>BAD PROGRAMMING. Object pointers ARE like ordinary pointers - if you (m)alloc
>something you should be responsible for it, and free it when its finished
>with (didn't your mother always tell you to put your toys away when you'd
>finished with them?).

Agreed.

>I also feel simlarly about messages to nil, but I guess I'm stuck with that
>one :-)

What's the reasoning behind allowing messages to nil? It seems a bit daft
because it makes debugging that much harder. If you have a nil object pointer
then just like any other pointer, you've probably made a mistake and the
sooner you find out, the better.

Dave Griffiths

Stefan Monnier

unread,
May 12, 1993, 11:00:40 AM5/12/93
to
In article <1993May11.0...@lth.se> d...@control.lth.se (Dag Bruck) writes:
>I think you should work some more on the discussion of GC. Your
>claims are not well supported, and imply that all applications have
>identical needs.

As a GC-addict, I must say that it is the main weakness of C++.
Not that the absence of GC makes some programs impossible, but GC
makes life so much easier. It is also very useful for developing nice
libraries:
When designing a C++ list class (a classic example, but those who
prefer stacks can use query/replace at will) you have to choose
whether to delete the elements in the list or to let the user delete
them. The more reasonable way to do it (I think) is to let the user
delete the elements. This is in no way a 'nice' library for me: each
time I remove an element from the list, I have to delete it myself.

Of course, there is a 'solution' which to provide two 'remove'
operations (deleting/not deleting). But still...it's clumsy

It's is particularly annoying because GC has very little disadvantages:
- GCs tend to block the program while collecting
- low-level work (in OSes f.ex.) doesn't match very well with GC
- the compiler becomes more complex

I think the first point should no longer hold, given the existence of
efficient incremental GCs, the second is not an issue because it's
much easier to 'not use' a GC than to simulate a non-existant one and
the last point is not very important because compilers tend to be more
complex anyway.

> -- Dag Bruck

I always thought animals were always taking advantage of comfort as
much as they could. But maybe human-beings are no real animals any more

Stefan

C. William Shirley

unread,
May 13, 1993, 12:07:20 AM5/13/93
to
In article <1993May11.185404.6789@prim> da...@prim.demon.co.uk (Dave Griffiths)
writes:

>
> What's the reasoning behind allowing messages to nil? It seems a bit daft
> because it makes debugging that much harder. If you have a nil object pointer
> then just like any other pointer, you've probably made a mistake and the
> sooner you find out, the better.
>
> Dave Griffiths

One (of many) examples of the usefullness of sending to nil:

- setIvar:anObject
{
// This allows the caller to dispose OR keep the previous object
id oldValue = ivar;
ivar = anObject;
return oldValue;
}


It becomes quite usefull to do
[[myInstance setIcar:anObject] free];

It is not an error to set the value of ivar when it is nil now.
Objective-C was designed to make code flow as simply as this.

Some GNUsters have suggested a Nil class that, by default,
behaves as it does now, but the functionality can be overrided/
augmented. I can see a value for this argument in debugging/
developing.

-Bill

--
Bill Shirley (NXceptable)
BShi...@GLeap.jpunix.com (home) | BShi...@ATG.WilTel.com (work)
``Every jumbled pile of person has a thinking part that wonders
what the part that isn't thinking isn't thinking of.'' - TMBG

Arnoud Martens

unread,
May 13, 1993, 3:23:38 AM5/13/93
to
In article <1993May11....@ohm.york.ac.uk>, i...@ohm.york.ac.uk (I Stephenson) writes:

> Garbage collection I say a big NO! Leaving an unreferenced object around is
> BAD PROGRAMMING. Object pointers ARE like ordinary pointers - if you (m)alloc
> something you should be responsible for it, and free it when its finished
> with (didn't your mother always tell you to put your toys away when you'd
> finished with them?).

Really don't agree. The nice thing about Objective C is that really encapsulates memory
management. I haven't seen many mallocs in our extensive Objective C code (BTW
Stepstone version)
And it's sometimes hard to tell where all the references to any particular
object goes. The problem with Objective C is, unlike the toys you should clean
up, you can make more references to the same object. When you think you've finished
off with the object and free it and somewhere else there is still a reference, yoy're
in deep trouble. Found that tracking these errors down can be real though. I think it
would be better if you don't have to care about this, just the same as when you use
a Factory method you don't have to care about all the malloc for instance variables.

Besides garbage collecting would make typical (and to my opinion very nice)
Objective C contructions like :

[anObject getThis] getThat] printYourselfToSomeStream] ;

more valid as this type of commands lead to a lot of garbage.

> Ian Stephenson
> University of York

--

+----------------------------------------------------------------------+
Arnoud Martens voice: +31-15-786057
Networktheory Section +31-15-563621 ($HOME)
Delft University of Technology
The Netherlands email: mar...@dutentb.et.tudelft.nl
+----------------------------------------------------------------------+

Gregory H. Anderson

unread,
May 12, 1993, 2:32:46 PM5/12/93
to
In article <1993May11.185404.6789@prim> da...@prim.demon.co.uk (Dave
Griffiths) writes:
> The more I think about it, the clearer the case becomes for adding class
> variables. A class is just an object like any other. It's a factory
> object. What better place to maintain Lists and HashTables of all the
> objects you've instantiated? I see no reason why a class shouldn't
> maintain state information. Someone earlier maintained that there was a
> philosophical problem with archiving. I disagree. The natural place to
> archive a collection of objects of class Foo is in Foo's +write factory
> method. There are also some objects that don't naturally have multiple
> copies. The "Controller" objects in the NeXT examples for instance.
> These are good candidates for true class objects with their own factory
> variables.

Agreed. That's why their absence ticks me off. This also causes problems
with subclasses that own instance of other subclasses, such as Views and
Cells in NEXTSTEP. We have six subclasses of TextField, each of which uses
its own unique subclass of TextFieldCell. You wouldn't believe the
aggravation to keep track of the class IDs at each level. With true class
variables, this would be a cinch, because each new subclass of TextField
would get its own class variable to hold its supporting Cell subclass.

>> I also feel simlarly about messages to nil, but I guess I'm stuck with
>> that one :-)
>
> What's the reasoning behind allowing messages to nil? It seems a bit
> daft because it makes debugging that much harder. If you have a nil
> object pointer then just like any other pointer, you've probably made a
> mistake and the sooner you find out, the better.

Allow me to give a useful example where 'nil' is not a mistake. In our
custom trading applications, the standard classes provide *supersets* of
ivars that are available in the database dictionary. Any given customer
might use 80%-90% of that potential, but it's impossible to say which. So
we had a choice: lowest common denominator (few ivars) in the base class,
and lots of duplicate ivars in custom subclasses; or highest common
denominator in the base class, and ignore the unnecessary ones in custom
subclasses. We chose the latter approach, and it works great. One of the
things that makes it work is the safety of messages to nil.

For example, suppose the database provides a BOOL for the potential
attribute 'foreign' (wrapped inside an object at the application level),
and a particular customer of ours doesn't do business in foreign
countries. They would likely leave this attribute off their UI, which
means the ivar would be unconnected (nil). Now suppose part of the trade
processing logic has a decision based on whether the broker is foreign. If
coded like this

if ([broker_isForeign state] == YES)
/* do something */
else
/* do something else */

it doesn't matter whether broker_isForeign is 'nil' or not. The code still
works as intended, because a system that doesn't care about the foreign
attribute would never branch to that code anyway. It would annoy me if I
always had to code the preceding construction

if ((broker_isForeign != nil) && ([broker_isForeign state] == YES))
/* do something */
else
/* do something else */

but that's what I would have to do if messages to nil were not allowed.
When you can't be sure whether an object will exist, but you can code the
program logic so it doesn't really matter, I think that's a win.

--
Gregory H. Anderson | "History, despite its wrenching pain,
Commander-in-Chief | Cannot be unlived, but if faced
Anderson Financial Systems | With courage, need not be lived again."
gr...@afs.com (Nextmail OK) | -- Maya Angelou, "On the Pulse of Morning"

Ed Cynwrig Dengler

unread,
May 14, 1993, 5:05:52 PM5/14/93
to
Greetings to all!


In article <C6x5pG...@cs.cmu.edu> monn...@cs.cmu.edu (Stefan Monnier) writes:
>
>It's is particularly annoying because GC has very little disadvantages:
>- GCs tend to block the program while collecting
>- low-level work (in OSes f.ex.) doesn't match very well with GC
>- the compiler becomes more complex
>
>I think the first point should no longer hold, given the existence of
>efficient incremental GCs, the second is not an issue because it's
>much easier to 'not use' a GC than to simulate a non-existant one and
>the last point is not very important because compilers tend to be more
>complex anyway.
>
>

>I always thought animals were always taking advantage of comfort as
>much as they could. But maybe human-beings are no real animals any more
>
> Stefan

I must agree with Stefan that these points are not important.

I am always amazed when the second point is dragged out as an argument
against garbage collectors. If a person is doing low level work such as
operating systems, then dynamic memory usage is almost NEVER used,
except when it would not cause a problem (ie. it is in some part of the
operating system that is not required to have an upper timing bound).
In all instances where time is critical, static memory allocation is
used (ie. no dynamic uses of 'malloc' and 'free', or 'new' and 'delete',
as used in the C world). Thus the argument that "garbage collection would
slow down by operating system" is meaningless since the memory used
will never be collected!

Ed
(who has spent enough time marking real-time operating systems to have
at least some idea what should and should not be there)

Richard Bielak

unread,
May 13, 1993, 9:28:12 AM5/13/93
to
In article <1so17...@uni-erlangen.de> be...@immd5.informatik.uni-erlangen.de (Ruediger Bess ) writes:

[...]

>
>I think to fix up dangling references dynamically will cause a
>big overhead in execution time.
>At every access to a pointer you have to test its validity.
>Is there any cheap possibility (in developing, in execution time and in
>the amount of storage for the memory management ) to fix
>this problem ?

Yes, there is a cheap solution to the dangling pointer problem.
Eliminate pointers!

For example, in Eiffel, with GC, you never get dangling pointers.
That's because objects do not get deleted by the programmer, they are
deleted by the GC which only deletes an object when no reference to
the it are left.

...richie

--
* Richie Bielak (212)-815-3072 | *
* Internet: ric...@bony.com | "When I don't know what I'm doing, *
* Bang {uupsi,uunet}!bony1!richieb | I'm doing basic research." *
* - Strictly my opinions - | - Wernher von Braun - *

Jon Rosen

unread,
May 14, 1993, 6:03:09 PM5/14/93
to

The problem with this solution is that it ONLY works when the message
being sent expects a return value of id or an integer (and then only
sometimes). If, on the other hand, you do the following:

if (strcmp[someObject stringValue],[someOtherObject stringValue])...

then this does NOT work if either object is nil. In fact, you might
get a very serious error.

In a true OOP where every return value is another object, this is not
a problem since all return values can then take messages whether there
is a real object returned or not. This is not true in a hybrid
language like Obj-C where you can return other kinds of data besides
object ids.

Just my $.02.

Jon Rosen

Pieter Schoenmakers

unread,
May 14, 1993, 2:04:09 PM5/14/93
to
In article <C6x5pG...@cs.cmu.edu> monn...@cs.cmu.edu (Stefan Monnier)
writes:

> It's is particularly annoying because GC has very little disadvantages:

> - the compiler becomes more complex

Huh? Care to explain? GC does not need modifications to the compiler;
only to the objects which want to be GC'able. --Tiggr

PS My view, of course...

Ruediger zu Dohna

unread,
May 15, 1993, 7:41:43 AM5/15/93
to
In article <1993May12.1...@afs.com>, gr...@afs.com (Gregory H.

Anderson) wrote:
> For example, suppose the database provides a BOOL for the potential
> attribute 'foreign' (wrapped inside an object at the application level),
> and a particular customer of ours doesn't do business in foreign
> countries. They would likely leave this attribute off their UI, which
> means the ivar would be unconnected (nil). Now suppose part of the trade
> processing logic has a decision based on whether the broker is foreign. If
> coded like this
>
> if ([broker_isForeign state] == YES)
> /* do something */
> else
> /* do something else */
>
> it doesn't matter whether broker_isForeign is 'nil' or not. The code still
> works as intended, because a system that doesn't care about the foreign
> attribute would never branch to that code anyway. It would annoy me if I
> always had to code the preceding construction
>
> if ((broker_isForeign != nil) && ([broker_isForeign state] == YES))
> /* do something */
> else
> /* do something else */
>
> but that's what I would have to do if messages to nil were not allowed.
> When you can't be sure whether an object will exist, but you can code the
> program logic so it doesn't really matter, I think that's a win.

You still have to think about the implications and to have a side effect is
really bad! It could be that you just wanted the behaviour to default for
foreigners. The language should not support producing correct programs by
accident. You _must_ find out about the implications and make them
explicit.
Bugs get harder to locate, when you _dont_ spot them by breaking code.

BTW "if(broker_isForeign && ([broker_isForeign state] == YES))" is nicer.


Mellow down easy!
Ruediger

Ruediger zu Dohna

unread,
May 15, 1993, 10:07:03 AM5/15/93
to
In article <C6x5pG...@cs.cmu.edu>, monn...@cs.cmu.edu (Stefan Monnier)
wrote:

> When designing a C++ list class (a classic example, but those who
> prefer stacks can use query/replace at will) you have to choose
> whether to delete the elements in the list or to let the user delete
> them. The more reasonable way to do it (I think) is to let the user
> delete the elements. This is in no way a 'nice' library for me: each
> time I remove an element from the list, I have to delete it myself.
>
> Of course, there is a 'solution' which to provide two 'remove'
> operations (deleting/not deleting). But still...it's clumsy

There are 3 ways to access a list:
Forget Dispose
1) Remove: yes yes
2) Get: yes no
3) Peek: no no

Is this 'solution' clumsy?


Mellow down easy!
Ruediger

Pieter Schoenmakers

unread,
May 15, 1993, 3:18:56 PM5/15/93
to
Solution to the [nil someMethod] problem: If you want the current
behaviour, be happy; if you don't want it, set a breakpoint in objc_msgSend
at the point where it discovers that a message is being sent to nil. (If
you're using GCC and and its libobjc.a, don't do this by putting debugging
information in (core.c)objc_msgSend, since stepping into a method then
steps into objc_msgSend. Set the breakpoint at the first machine
instruction after the comparison to nil succeeds.) --Tiggr

Robert Martin

unread,
May 16, 1993, 5:30:28 PM5/16/93
to
monn...@cs.cmu.edu (Stefan Monnier) writes:
[...regarding garbage collection...]

>I always thought animals were always taking advantage of comfort as
>much as they could. But maybe human-beings are no real animals any more

I quote from (memory) the Declaration of Independence:

"...and accordingly all experience hath shown that
mankind are more disposed to suffer while evils are
sufferable than to right themselves by abolishing
the forms to which they are accustomed..."


--
Robert Martin | Design Consulting |Training courses offered:
R.C.M. Consulting | rcm!rma...@uunet.UU.NET| Object Oriented Analysis
2080 Cranbrook Rd. | Tel: (708) 918-1004 | Object Oriented Design
Green Oaks IL 60048| Fax: (708) 918-1023 | C++

Jon Rosen

unread,
May 17, 1993, 12:00:32 AM5/17/93
to

This, of course, is one of those "it really all depends" type of issues.
In a strict OOP language like Smalltalk where EVERYTHING is instantiated
as an object, by definition the only way the reference to an object can
be taken or given is by sending the object a message which returns a
reference to itself. Thus, a base Object class can be designed which
contains all the necessary "stuff" needed to keep accurate reference
counts or reference tables, so that GC is relatively disjoint from the
compiler. In a hybrid compiler, like Obj-C or C++, on the other hand,
you have the concept of taking absolute pointers to things. This means
that references to objects can be generated without the use of the object
system making it almost impossible to implement GC without the help of
and modification of the compiler to detect or prevent such actions.

Jon Rosen

Richard M. Hartman

unread,
May 17, 1993, 2:14:06 PM5/17/93
to
In article <dohna-150...@i02mac191.ira.uka.de> do...@ira.uka.de (Ruediger zu Dohna) writes:
>In article <C6x5pG...@cs.cmu.edu>, monn...@cs.cmu.edu (Stefan Monnier)
>wrote:
>> Of course, there is a 'solution' which to provide two 'remove'
>> operations (deleting/not deleting). But still...it's clumsy
>
>There are 3 ways to access a list:
> Forget Dispose
>1) Remove: yes yes
>2) Get: yes no
>3) Peek: no no
>
>Is this 'solution' clumsy?

For a Queue or Stack I use this kind of nomenclature frequently,
but there is a basic assumption (that I make) about the purpose
of a Queue or Stack vs. the purpose of a List.

A Queue or Stack is there to organize & hold things until I can
get around to processing them. Therefore a "get" that removes
them from the list makes sense. As does a "peek" (which would
be the only way to get at anything that was not on the top
of the Queue or Stack anyway).

A List (to me) represents a more permanent form of storage, and
when I get something from a list it is assumed that it will still
be there the next time I look for it. It is just a matter of
semantics, but I don't like to use the term "peek" for this
sort of access. When working with lists I prefer to always
assume the list-owner is responsible for deleting list elements,
"delete list.remove(key);" is not that difficult to write, and
who knows, there may be some clean-up operations to perform
w/ the object anyway, so would you rather do:

p = list.lookup(key);
// clean-up using p
list.remove(key); // incurring penalty of key lookup again

or

p = list.remove(key); // unlink p from list
// clean-up using p
delete p;

However, if you did want to use all three types of access
mentioned above for lists I (personally) would rename them:

remove delete
get: no no
unlink: yes no
destroy: yes yes

Or something along those lines, with the function that implies
memory deallocation and the one that does not named so that there
is no ambiguity about what they do so there is no wondering about
"does remove() delete the object, or just unlink it..."

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Blasting, bursting, billowing forth with |
the power of ten billion butterfly sneezes, | -Richard Hartman
Man, with his flaming fire, | har...@uLogic.COM
has conquered the wayword breezes. |

Pieter Schoenmakers

unread,
May 17, 1993, 12:57:46 PM5/17/93
to
In article <1993May17.0...@RedBrick.COM> j...@RedBrick.COM (Jon
Rosen) writes:

>In a hybrid compiler, like Obj-C or C++, on the other hand,
>you have the concept of taking absolute pointers to things. This means
>that references to objects can be generated without the use of the object
>system making it almost impossible to implement GC without the help of
>and modification of the compiler to detect or prevent such actions.

If everything is an object or at least managed by one, which is necessary
for GC, GC can be performed with a little help of the objects which reside
on the GC'able part of the heap. The compiler does not need to help. In
fact, the compiler can't help since there is always at least one way to
obtain a pointer to something to which you're not supposed to have a
pointer. --Tiggr

Kresten Krab Thorup

unread,
May 18, 1993, 5:29:49 AM5/18/93
to

How about object pointed to from the stack -- if they're currently in
a local variable which is about to be assigned to an instance
variable. Then your mechanism will loose unless it scans the stack.
This also applies for registers.

/Kresten

Kresten Krab Thorup

unread,
May 18, 1993, 5:58:13 AM5/18/93
to

If everything is an object or at least managed by one, which is necessary
for GC, GC can be performed with a little help of the objects which reside
on the GC'able part of the heap.

The minimal necessarity for GC is that there should exist a pointer to
an memory region which is not supposed to be reclaimed. Being
`managed' is a somewhat weak statement, but I suspect you mean `having
as an instance variable'.

In my oppinion we could do GC in objc in either of two ways.

1 Having some simple `ReferenceCounting' protocol which is under-
stood by collection classes and the like. If objects do not
conform to this, we will not worry about it.

2 Do full fledged reference counting at a lower level using something
like Boehms garbage collector.

I think the second one is much better. This way we'll get rid of a
lot of nasty and slow code... Boehms collector would be quite easy to
incorporate into the GNU runtime, since it has only one mention of
malloc :-) However, on the NeXT it'll be a problem to replace the
memory regions. I've included the readme file from Boehms free
garbage collector below.

/Kresten


Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
Copyright (c) 1991, 1992 by Xerox Corporation. All rights reserved.

THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
OR IMPLIED. ANY USE IS AT YOUR OWN RISK.

Permission is hereby granted to copy this garbage collector for any purpose,
provided the above notices are retained on all copies.


This is version 2.5. Note that functions were renamed since version 1.9
to make naming consistent with PCR collectors.

HISTORY -

Early versions of this collector were developed as a part of research
projects supported in part by the National Science Foundation
and the Defense Advance Research Projects Agency.
The SPARC specific code was contributed by Mark Weiser
(wei...@parc.xerox.com). The Encore Multimax modifications were supplied by
Kevin Kenny (ke...@m.cs.uiuc.edu). The adaptation to the RT is largely due
to Vernon Lee (scor...@rice.edu), on machines made available by IBM.
Much of the HP specific code and a number of good suggestions for improving the
generic code are due to Walter Underwood (wun...@hp-ses.sde.hp.com).
Robert Brazile (bra...@diamond.bbn.com) originally supplied the ULTRIX code.
Al Dosser (dos...@src.dec.com) and Regis Cridlig (Regis....@cl.cam.ac.uk)
subsequently provided updates and information on variation between ULTRIX
systems. Parag Patel (pa...@netcom.com) supplied the A/UX code.
Bill Janssen (jan...@parc.xerox.com) supplied the SunOS dynamic loader
specific code. Manuel Serrano (ser...@cornas.inria.fr) supplied linux and
Sony News specific code.

(Blame for misinstallation of those modifications goes to the first author,
however.) Some of the improvements incorporated in this version were
suggested by David Chase, then at Olivetti Research.

Much of the code was rewritten by Hans-J. Boehm at Xerox PARC.

This is intended to be a general purpose, garbage collecting storage
allocator. The algorithms used are described in:

Boehm, H., and M. Weiser, "Garbage Collection in an Uncooperative Environment",
Software Practice & Experience, September 1988, pp. 807-820.

Some of the ideas underlying the collector have previously been explored
by others. (Doug McIlroy wrote a vaguely similar collector that is part of
version 8 UNIX (tm).) However none of this work appears to have been widely
disseminated.

This collector includes numerous refinements not described in the above paper.

Rudimentary tools for use of the collector as a leak detector are included.


GENERAL DESCRIPTION

This is a garbage colecting storage allocator that is intended to be
used as a plug-in replacement for C's malloc.

Since the collector does not require pointers to be tagged, it does not
attempt to ensure that all inaccessible storage is reclaimed. However,
in our experience, it is typically more successful at reclaiming unused
memory than most C programs using explicit deallocation. Unlike manually
introduced leaks, the amount of unreclaimed memory typically stays
bounded.

In the following, an "object" is defined to be a region of memory allocated
by the routines described below.

Any objects not intended to be collected must be pointed to either
from other such accessible objects, or from the registers,
stack, data, or statically allocated bss segments. Pointers from
the stack or registers may point to anywhere inside an object.
However, it is usually assumed that all pointers originating in the
heap point to the beginning of an object. (This does
not disallow interior pointers; it simply requires that there must be a
pointer to the beginning of every accessible object, in addition to any
interior pointers.) There are two facilities for altering this behavior.
The macro ALL_INTERIOR_POINTERS may be defined in gc_private.h to
cause any pointer into an object to retain the object. A routine
GC_register_displacement is provided to allow for more controlled
interior pointer use in the heap. Defining ALL_INTERIOR_POINTERS
is somewhat dangerous. See gc_private.h for details. The routine
GC_register_displacement is described in gc.h.

Note that pointers inside memory allocated by the standard "malloc" are not
seen by the garbage collector. Thus objects pointed to only from such a
region may be prematurely deallocated. It is thus suggested that the
standard "malloc" be used only for memory regions, such as I/O buffers, that
are guaranteed not to contain pointers. Pointers in C language automatic,
static, or register variables, are correctly recognized.

The collector does not generally know how to find pointers in data
areas that are associated with dynamic libraries. This is easy to
remedy IF you know how to find those data areas on your operating
system (see GC_add_roots). Code for doing this under SunOS4.X only is
included (see dynamic_load.c). (Note that it includes a special version
of dlopen, GC_dlopen, that should be called instead of the standard one.
By default, this is not compiled in, since it requires the -ldl library.)
Note that the garbage collector does not need to be informed of shared
read-only data. However if the shared library mechanism can introduce
discontiguous data areas that may contain pointers, then the collector does
need to be informed.

Signal processing for most signals is normally deferred during collection,
and during uninterruptible parts of the allocation process. Unlike
standard ANSI C mallocs, it is intended to be safe to invoke malloc
from a signal handler while another malloc is in progress, provided
the original malloc is not restarted. (Empirically, many UNIX
applications already asssume this.) The allocator/collector can
also be configured for thread-safe operation. (Full signal safety can
also be acheived, but only at the cost of two system calls per malloc,
which is usually unacceptable.)

INSTALLATION AND PORTABILITY

As distributed, the macro SILENT is defined at the top of gc_private.h.
In the event of problems, this can be removed to obtain a moderate
amount of descriptive output for each collection.
(The given statistics exhibit a few peculiarities.
Things don't appear to add up for a variety of reasons, most notably
fragmentation losses. These are probably much more significant for the
contrived program "test.c" than for your application.)

Note that typing "make test" will automatically build the collector
and then run setjmp_test and gctest. Setjmp_test will give you information
about configuring the collector, which is useful primarily if you have
a machine that's not already supported. Gctest is a somewhat superficial
test of collector functionality. Failure is indicated by a core dump or
a message to the effect that the collector is broken. Gctest takes about
20 seconds to run on a SPARCstation 2. On a slower machine,
expect it to take a while. It may use up to 8 MB of memory. (The
multi-threaded version will use more.)

The Makefile will generate a library gc.a which you should link against.
It is suggested that if you need to replace a piece of the collector
(e.g. GC_mark_roots.c) you simply list your version ahead of gc.a on the
ld command line, rather than replacing the one in gc.a. (This will
generate numerous warnings under some versions of AIX, but it still
works.)

The collector currently is designed to run essentially unmodified on
the following machines:

Sun 3
Sun 4 under SunOS 4.X or Solaris2.X
Vax under 4.3BSD, Ultrix
Intel 386 or 486 under OS/2 (no threads) or linux.
Sequent Symmetry (no concurrency)
Encore Multimax (no concurrency)
MIPS M/120 (and presumably M/2000) (RISC/os 4.0 with BSD libraries)
IBM PC/RT (Berkeley UNIX)
IBM RS/6000
HP9000/300
HP9000/700
DECstations under Ultrix
SGI workstations under IRIX
Sony News
Apple MacIntosh under A/UX

For these machines you should check the beginning of gc.h
to verify that the machine type is correctly defined. On
nonSun machines, you may also need to make changes to the
Makefile, as described by comments there.

Dynamic libraries are completely supported only under SunOS4.X
(and even that support is not functional on the last Sun 3 release).
On other machines we recommend that you do one of the following:

1) Add dynamic library support (and send us the code).
2) Use static versions of the libraries.
3) Arrange for dynamic libraries to use the standard malloc.
This is still dangerous if the library stores a pointer to a
garbage collected object. But nearly all standard interfaces
prohibit this, because they deal correctly with pointers
to stack allocated objects. (Strtok is an exception. Don't
use it.)

In all cases we assume that pointer alignment is consistent with that
enforced by the standard C compilers. If you use a nonstandard compiler
you may have to adjust the alignment parameters defined in gc_private.h.

A port to a machine that is not byte addressed, or does not use 32 bit
addresses will require a major effort. (Parts of the code try to anticipate
64 bit addresses. Others will need to be rewritten, since different data
structures are needed.) A port to MSDOS is hopeless, unless you are willing
to assume an 80386 or better, and that only flat 32 bit pointers will ever be
used.

For machines not already mentioned, or for nonstandard compilers, the
following are likely to require change:

1. The parameters at the top of gc_private.h.
The parameters that will usually require adjustment are
STACKBOTTOM, ALIGNMENT and DATASTART. Setjmp_test
prints its guesses of the first two.
DATASTART should be an expression for computing the
address of the beginning of the data segment. This can often be
&etext. But some memory management units require that there be
some unmapped space between the text and the data segment. Thus
it may be more complicated. On UNIX systems, this is rarely
documented. But the adb "$m" command may be helpful. (Note
that DATASTART will usually be a function of &etext. Thus a
single experiment is usually insufficient.)
STACKBOTTOM is used to initialize GC_stackbottom, which
should be a sufficient approximation to the coldest stack address.
On some machines, it is difficult to obtain such a value that is
valid across a variety of MMUs, OS releases, etc. A number of
alternatives exist for using the collector in spite of this. See the
discussion in gc_private.h immediately preceding the various
definitions of STACKBOTTOM.

2. mach_dep.c.
The most important routine here is one to mark from registers.
The distributed file includes a generic hack (based on setjmp) that
happens to work on many machines, and may work on yours. Try
compiling and running setjmp_test.c to see whether it has a chance of
working. (This is not correct C, so don't blame your compiler if it
doesn't work. Based on limited experience, register window machines
are likely to cause trouble. If your version of setjmp claims that
all accessible variables, including registers, have the value they
had at the time of the longjmp, it also will not work. Vanilla 4.2 BSD
makes such a claim. SunOS does not.)
If your compiler does not allow in-line assembly code, or if you prefer
not to use such a facility, mach_dep.c may be replaced by a .s file
(as we did for the MIPS machine and the PC/RT).

3. mark_roots.c.
These are the top level mark routines that determine which sections
of memory the collector should mark from. This is normally not
architecture specific (aside from the macros defined in gc_private.h and
referenced here), but it can be programming language and compiler
specific. The supplied routine should work for most C compilers
running under UNIX. Calls to GC_add_roots may sometimes be used
for similar effect.

4. The sigsetmask call does not appear to exist under early system V UNIX.
It is used by the collector to block and unblock signals at times at
which an asynchronous allocation inside a signal handler could not
be tolerated. Under system V, it is possible to remove these calls,
provided no storage allocation is done by signal handlers. The
alternative is to issue a sequence of system V system calls, one per
signal that is actually used. This may be a bit slow.

For a different versions of Berkeley UN*X or different machines using the
Motorola 68000, Vax, SPARC, 80386, NS 32000, PC/RT, or MIPS architecture,
it should frequently suffice to change definitions in gc_private.h.


THE C INTERFACE TO THE ALLOCATOR

The following routines are intended to be directly called by the user.
Note that usually only GC_malloc is necessary. GC_clear_roots and GC_add_roots
calls may be required if the collector has to trace from nonstandard places
(e.g. from dynamic library data areas on a machine on which the
collector doesn't already understand them.) On some machines, it may
be desirable to set GC_stacktop to a good approximation of the stack base.
(This enhances code portability on HP PA machines, since there is no
good way for the collector to compute this value.) Client code may include
"gc.h", which defines all of the following, plus a few others.

1) GC_malloc(nbytes)
- allocate an object of size nbytes. Unlike malloc, the object is
cleared before being returned to the user. Gc_malloc will
invoke the garbage collector when it determines this to be appropriate.
GC_malloc may return 0 if it is unable to acquire sufficient
space from the operating system. This is the most probable
consequence of running out of space. Other possible consequences
are that a function call will fail due to lack of stack space,
or that the collector will fail in other ways because it cannot
maintain its internal data structures, or that a crucial system
process will fail and take down the machine. Most of these
possibilities are independent of the malloc implementation.

2) GC_malloc_atomic(nbytes)
- allocate an object of size nbytes that is guaranteed not to contain any
pointers. The returned object is not guaranteed to be cleeared.
(Can always be replaced by GC_malloc, but results in faster collection
times. The collector will probably run faster if large character
arrays, etc. are allocated with GC_malloc_atomic than if they are
statically allocated.)

3) GC_realloc(object, new_size)
- change the size of object to be new_size. Returns a pointer to the
new object, which may, or may not, be the same as the pointer to
the old object. The new object is taken to be atomic iff the old one
was. If the new object is composite and larger than the original object,
then the newly added bytes are cleared (we hope). This is very likely
to allocate a new object, unless MERGE_SIZES is defined in gc_private.h.
Even then, it is likely to recycle the old object only if the object
is grown in small additive increments (which, we claim, is generally bad
coding practice.)

4) GC_free(object)
- explicitly deallocate an object returned by GC_malloc or
GC_malloc_atomic. Not necessary, but can be used to minimize
collections if performance is critical.

5) GC_expand_hp(number_of_4K_blocks)
- Explicitly increase the heap size. (This is normally done automatically
if a garbage collection failed to GC_reclaim enough memory. Explicit
calls to GC_expand_hp may prevent unnecessarily frequent collections at
program startup.)

6) GC_clear_roots()
- Reset the collectors idea of where static variables containing pointers
may be located to the empty set of locations. No statically allocated
variables will be traced from after this call, unless there are
intervening GC_add_roots calls. The collector will still trace from
registers and the program stack.

7) GC_add_roots(low_address, high_address_plus_1)
- Add [low_address, high_address) as an area that may contain root pointers
and should be traced by the collector. The static data and bss segments
are considered by default, and should not be added unless GC_clear_roots
has been called. The number of root areas is currently limited to 50.
This is intended as a way to register data areas for dynamic libraries,
or to replace the entire data ans bss segments by smaller areas that are
known to contain all the roots.

8) Several routines to allow for registration of finalization code.
User supplied finalization code may be invoked when an object becomes
unreachable. To call (*f)(obj, x) when obj becomes inaccessible, use
GC_register_finalizer(obj, f, x, 0, 0);
For more sophisticated uses, and for finalization ordering issues,
see gc.h.

The global variable GC_free_space_divisor may be adjusted up from its
default value of 4 to use less space and more collection time, or down for
the opposite effect. Setting it to 1 or 0 will effectively disable collections
and cause all allocations to simply grow the heap.

The variable GC_non_gc_bytes, which is normally 0, may be changed to reflect
the amount of memory allocated by the above routines that should not be
considered as a candidate for collection. Careless use may, of course, result
in excessive memory consumption.

Some additional tuning is possible through the parameters defined
near the top of gc_private.h.

If only GC_malloc is intended to be used, it might be appropriate to define:

#define malloc(n) GC_malloc(n)
#define calloc(m,n) GC_malloc((m)*(n))

For small pieces of VERY allocation intensive code, gc_inline.h
includes some allocation macros that may be used in place of GC_malloc
and friends.

Somewhat different emulations of the standard C allocation routines are
contained and described in "interface.c" (contributed by David Chase, but
subsequently mangled by Hans Boehm). These are appropriate for mixed
systems, where part of the system uses explicit deallocation, and does not
leak. Exclusive use of interface.c routines can result in needless
fragmentation, since certain kinds of object coalescing are only done
by the collector.

All externally visible names in the garbage collector start with "GC_".
To avoid name conflicts, client code should avoid this prefix, except when
accessing garbage collector routines or variables.

The internals of the collector understand different object "kinds" (sometimes
called "regions"). By default, the only two kinds are ATOMIC and NORMAL.
Its should be possible to add others, e.g. for data types for which layout
information is known. The allocation routine "GC_generic_malloc"
takes an explicit kind argument. (You will probably want to add
faster kind-specific routines as well.) You will need to add another kind
descriptor, including your own mark routine to add a new object kind.
This requires a fairly detailed understanding of at least GC_mark.


USE AS LEAK DETECTOR:

The collector may be used to track down leaks in C programs that are
intended to run with malloc/free (e.g. code with extreme real-time or
portability constraints). To do so define FIND_LEAK somewhere in
gc_private.h. This will cause the collector to invoke the report_leak
routine defined near the top of reclaim.c whenever an inaccessible
object is found that has not been explicitly freed.
Productive use of this facility normally involves redefining report_leak
to do something more intelligent. This typically requires annotating
objects with additional information (e.g. creation time stack trace) that
identifies their origin. Such code is typically not very portable, and is
not included here.
If all objects are allocated with GC_DEBUG_MALLOC (see next section),
then the default version of report_leak will report the source file
and line number at which the leaked object was allocated. This may
sometimes be sufficient.


DEBUGGING FACILITIES:

The routines GC_debug_malloc, GC_debug_malloc_atomic, GC_debug_realloc,
and GC_debug_free provide an alternate interface to the collector, which
provides some help with memory overwrite errors, and the like.
Objects allocated in this way are annotated with additional
information. Some of this information is checked during garbage
collections, and detected inconsistencies are reported to stderr.

Simple cases of writing past the end of an allocated object should
be caught if the object is explicitly deallocated, or if the
collector is invoked while the object is live. The first deallocation
of an object will clear the debugging info associated with an
object, so accidentally repeated calls to GC_debug_free will report the
deallocation of an object without debugging information. Out of
memory errors will be reported to stderr, in addition to returning
NIL.

GC_debug_malloc checking during garbage collection is enabled
with the first call to GC_debug_malloc. This will result in some
slowdown during collections. If frequent heap checks are desired,
this can be acheived by explicitly invoking GC_gcollect, e.g. from
the debugger.

GC_debug_malloc allocated objects should not be passed to GC_realloc
or GC_free, and conversely. It is however acceptable to allocate only
some objects with GC_debug_malloc, and to use GC_malloc for other objects,
provided the two pools are kept distinct. In this case, there is a very
low probablility that GC_malloc allocated objects may be misidentified as
having been overwritten. This should happen with probability at most
one in 2**32. This probability is zero if GC_debug_malloc is never called.

GC_debug_malloc, GC_malloc_atomic, and GC_debug_realloc take two
additional trailing arguments, a string and an integer. These are not
interpreted by the allocator. They are stored in the object (the string is
not copied). If an error involving the object is detected, they are printed.

The macros GC_MALLOC, GC_MALLOC_ATOMIC, GC_REALLOC, GC_FREE, and
GC_REGISTER_FINALIZER are also provided. These require the same arguments
as the corresponding (nondebugging) routines. If gc.h is included
with GC_DEBUG defined, they call the debugging versions of these
functions, passing the current file name and line number as the two
extra arguments, where appropriate. If gc.h is included without GC_DEBUG
defined, then all these macros will instead be defined to their nondebugging
equivalents. (GC_REGISTER_FINALIZER is necessary, since pointers to
objects with debugging information are really pointers to a displacement
of 16 bytes form the object beginning, and some translation is necessary
when finalization routines are invoked. For details, about what's stored
in the header, see the definition of the type oh in debug_malloc.c)

INCREMENTAL/GENERATIONAL COLLECTION:

The collector normally interrupts client code for the duration of
a garbage collection mark phase. This may be unacceptable if interactive
response is needed for programs with large heaps. The collector
can also run in a "generational" mode, in which it usually attempts to
collect only objects allocated since the last garbage collection.
Furthermore, in this mode, garbage collections run mostly incrementally,
with a small amount of work performed in response to each of a large number of
GC_malloc requests.

This mode is enabled by a call to GC_enable_incremental().

Incremental and generational collection is effective in reducing
pause times only if the collector has some way to tell which objects
or pages have been recently modified. The collector uses two sources
of information:

1. Information provided by the VM system. This may be provided in
one of several forms. Under Solaris 2.X (and potentially under other
similar systems) information on dirty pages can be read from the
/proc file system. Under other systems (currently SunOS4.X) it is
possible to write-protect the heap, and catch the resulting faults.
On these systems we require that system calls writing to the heap
(other than read) be handled specially by client code.
See os_dep.c for details.

2. Information supplied by the programmer. We define "stubborn"
objects to be objects that are rarely changed. Such an object
can be allocated (and enabled for writing) with GC_malloc_stubborn.
Once it has been initialized, the collector should be informed with
a call to GC_end_stubborn_change. Subsequent writes that store
pointers into the object must be preceded by a call to
GC_change_stubborn.

This mechanism performs best for objects that are written only for
initialization, and such that only one stubborn object is writable
at once. It is typically not worth using for short-lived
objects. Stubborn objects are treated less efficiently than pointerfree
(atomic) objects.

A rough rule of thumb is that, in the absence of VM information, garbage
collection pauses are proportional to the amount of pointerful storage
plus the amount of modified "stubborn" storage that is reachable during
the collection.

Initial allocation of stubborn objects takes longer than allocation
of other objects, since other data structures need to be maintained.

We recommend against random use of stubborn objects in client
code, since bugs caused by inappropriate writes to stubborn objects
are likely to be very infrequently observed and hard to trace.
However, their use may be appropriate in a few carefully written
library routines that do not make the objects themselves available
for writing by client code.


BUGS:

Any memory that does not have a recognizable pointer to it will be
reclaimed. Exclusive-or'ing forward and backward links in a list
doesn't cut it.
Some C optimizers may lose the last undisguised pointer to a memory
object as a consequence of clever optimizations. This has almost
never been observed in practice. Send mail to bo...@parc.xerox.com
for suggestions on how to fix your compiler.
This is not a real-time collector. In the standard configuration,
percentage of time required for collection should be constant across
heap sizes. But collection pauses will increase for larger heaps.
(On SPARCstation 2s collection times will be on the order of 300 msecs
per MB of accessible memory that needs to be scanned. Your mileage
may vary.) The incremental/generational collection facility helps,
but is portable only if "stubborn" allocation is used.

RECENT VERSIONS:

Version 1.3 and immediately preceding versions contained spurious
assembly language assignments to TMP_SP. Only the assignment in the PC/RT
code is necessary. On other machines, with certain compiler options,
the assignments can lead to an unsaved register being overwritten.
Known to cause problems under SunOS 3.5 WITHOUT the -O option. (With
-O the compiler recognizes it as dead code. It probably shouldn't,
but that's another story.)

Version 1.4 and earlier versions used compile time determined values
for the stack base. This no longer works on Sun 3s, since Sun 3/80s use
a different stack base. We now use a straightforward heuristic on all
machines on which it is known to work (incl. Sun 3s) and compile-time
determined values for the rest. There should really be library calls
to determine such values.

Version 1.5 and earlier did not ensure 8 byte alignment for objects
allocated on a sparc based machine.

Please address bug reports to bo...@xerox.com. If you are contemplating
a major addition, you might also send mail to ask whether it's already
been done.

Version 1.8 added ULTRIX support in gc_private.h.

Version 1.9 fixed a major bug in gc_realloc.

Version 2.0 introduced a consistent naming convention for collector
routines and added support for registering dynamic library data segments
in the standard mark_roots.c. Most of the data structures were revamped.
The treatment of interior pointers was completely changed. Finalization
was added. Support for locking was added. Object kinds were added.
We added a black listing facility to avoid allocating at addresses known
to occur as integers somewhere in the address space. Much of this
was accomplished by adapting ideas and code from the PCR collector.
The test program was changed and expanded.

Version 2.1 was the first stable version since 1.9, and added support
for PPCR.

Version 2.2 added debugging allocation, and fixed various bugs. Among them:
- GC_realloc could fail to extend the size of the object for certain large object sizes.
- A blatant subscript range error in GC_printf, which unfortunately
wasn't excercised on machines with sufficient stack alignment constraints.
- GC_register_displacement did the wrong thing if it was called after
any allocation had taken place.
- The leak finding code would eventually break after 2048 byte
byte objects leaked.
- interface.c didn't compile.
- The heap size remained much too small for large stacks.
- The stack clearing code behaved badly for large stacks, and perhaps
on HP/PA machines.

Version 2.3 added ALL_INTERIOR_POINTERS and fixed the following bugs:
- Missing declaration of etext in the A/UX version.
- Some PCR root-finding problems.
- Blacklisting was not 100% effective, because the plausible future
heap bounds were being miscalculated.
- GC_realloc didn't handle out-of-memory correctly.
- GC_base could return a nonzero value for addresses inside free blocks.
- test.c wasn't really thread safe, and could erroneously report failure
in a multithreaded environment. (The locking primitives need to be
replaced for other threads packages.)
- GC_CONS was thoroughly broken.
- On a SPARC with dynamic linking, signals stayed diabled while the
client code was running.
(Thanks to Manuel Serrano at INRIA for reporting the last two.)

Version 2.4 added GC_free_space_divisor as a tuning knob, added
support for OS/2 and linux, and fixed the following bugs:
- On machines with unaligned pointers (e.g. Sun 3), every 128th word could
fail to be considered for marking.
- Dynamic_load.c erroneously added 4 bytes to the length of the data and
bss sections of the dynamic library. This could result in a bad memory
reference if the actual length was a multiple of a page. (Observed on
Sun 3. Can probably also happen on a Sun 4.)
(Thanks to Robert Brazile for pointing out that the Sun 3 version
was broken. Dynamic library handling is still broken on Sun 3s
under 4.1.1U1, but apparently not 4.1.1. If you have such a machine,
use -Bstatic.)

Version 2.5 fixed the following bugs:
- Removed an explicit call to exit(1)
- Fixed calls to GC_printf and GC_err_printf, so the correct number of
arguments are always supplied. The OS/2 C compiler gets confused if
the number of actuals and the number of formals differ. (ANSI C
doesn't require this to work. The ANSI sanctioned way of doing things
causes too many compatibility problems.)

Version 3.0 added generational/incremental collection and stubborn
objects.

David Apfelbaum

unread,
May 18, 1993, 8:28:42 PM5/18/93
to
Excerpts from netnews.comp.lang.objective-c: 18-May-93 Garbage
Collection by Kresten Krab Thorup@iesd
> 1 Having some simple `ReferenceCounting' protocol which is under-
> stood by collection classes and the like. If objects do not
> conform to this, we will not worry about it.


Actually, the first one is ALREADY implemented under NeXTStep 3.0.

Look at the NXReference Protocol in MachKit.

It's implemented by:

IXFileFinder
IXStoreBlock
NXConnection
NXInvalidationNotifier
NXProxy

My $.02... Personally, I think the solution *should* be to write
good code... And use MallocDebug.app to find and remove any mistakes
(leaks).

-David.

Pieter Schoenmakers

unread,
May 18, 1993, 2:10:39 PM5/18/93
to
In article <KRAB.93Ma...@xiv.iesd.auc.dk> kr...@iesd.auc.dk (Kresten
Krab Thorup) writes:

> How about object pointed to from the stack -- if they're currently in
> a local variable which is about to be assigned to an instance
> variable. Then your mechanism will loose unless it scans the stack.

So, make sure it is referenced by some object which is referenced by some
object which... ...is locked. This is easy. Besides, I strongly dislike
the idea of screening allocated memory and data, bss and stack segments,
looking for pointers... --Tiggr

Stephen P Spackman

unread,
May 22, 1993, 9:34:48 PM5/22/93
to
In article <C6yw3...@bony1.bony.com> ric...@bony1.bony.com (Richard Bielak) writes:

|Yes, there is a cheap solution to the dangling pointer problem.
|Eliminate pointers!
|
|For example, in Eiffel, with GC, you never get dangling pointers.
|That's because objects do not get deleted by the programmer, they are
|deleted by the GC which only deletes an object when no reference to
|the it are left.

You ever debugged a garbage collector? From my perspective, garbage
collection *causes* dangling pointers! ;-)
----------------------------------------------------------------------
stephen p spackman +49 681 302 5288(o) 5282(sec) ste...@acm.org
dfki / stuhlsatzenhausweg 3 / d-w-6600 saarbruecken 11 / germany
----------------------------------------------------------------------

0 new messages