Making a Case for Animating C++ Programs

Alan argues that for object-oriented systems, it's better to have dynamic, object-oriented, animated views that show objects as they are created and destroyed and as they communicate than it is to animate static structural views.


October 01, 1994
URL:http://www.drdobbs.com/cpp/making-a-case-for-animating-c-programs/184409325

OCT94: Making a Case for Animating C++ Programs

Do new styles of programming require new kinds of tools?

Alan is the chief architect of Look!, a C++ animation system. He can be reached at [email protected] or [email protected].


The move to object-oriented languages is paralleling the rise of complex, event-driven, GUI-based applications which use object-oriented frameworks to partition and encapsulate their functionality. Unfortunately, there are few tools currently available that help you understand the dynamic aspects of object programs. CASE tools do allow certain design diagrams to be reverse engineered, showing class inheritance, containment, and use relationships, but these are static and show the structure of the code rather than indicate how it functions.

Clearly, the code itself is the main source of information about dynamic activity, even though most debuggers used for examining C++ programs in action present a procedural stream of executed source lines. Up to now, the use of procedural tools to debug and understand C++ programs hasn't been a stumbling block, since many programmers still don't utilize all of C++'s object-oriented capabilities. But as C++ programs begin to become truly object oriented, problems will arise when viewing execution of object systems as a sequential execution of source code. For instance, you'll need to focus on the changing set of objects that make up the system and the interactions between objects. This information supplements--not replaces--that provided by static tools. When it comes to understanding and debugging programs, the more information and data points you have, the better off you usually are.

Some would say that dynamic views of program execution are unnecessary, overly confusing, and generally not worth the effort. In this article, I'll argue that object-oriented systems require dynamic, object-oriented, animated views of C++ programs. For the purpose of example, I'll refer to Look! (which I designed), which lets you "look" inside Windows- or UNIX-based C++ programs as they execute. Figure 1 shows a typical Look! session. Although Look! is currently perhaps the only tool providing a peek at program execution that is both dynamic and visual, other tools support one or the other. For instance, Pure Software's Purify gives you a dynamic view of memory, although not yet a visual one, while the TVIEW component of Nu-Mega's Bounds-Checker32/S debugger provides you with a visual, albeit static, view.


Figure 1: Typical creation-hierarchy view of a paint program.


Smalltalk programmers have long been familiar with object inspectors that allow object-status examination while the sys-tem is running. Likewise, in his article, A Minimal Object-Oriented Debugger for C++" (DDJ, October 1991), William Miller presented a simple debugger that lets you look at objects being created and destroyed while the program was running. Other work in this vein includes animating processes in concurrent systems (see "An Extensible Distributed Object Management System, EDOMS" by Burke, Domae, and Johnson, Proceedings of the Second International Conference TOOLS, 1990) and animating algorithm execution ("Algorithm Animation" by M.H. Brown, ACM Distinguished Dissertations Series, MIT Press, 1988). Other projects have concentrated on the animation of static displays of program components, usually using Prolog, ("An Integrated Prolog Programming Environment" by Schreiweis, Keune, and Langendorfer, ACM Sigplan Notices, February 1993) or Lisp-derivative languages ("GraphTrace: Understanding Object-Oriented Systems Using Concurrently Animated Views" by Kleyn and Gingritch, ACM Sigplan Notices, November 1988). The GraphTrace program aimed to increase the understanding of object programs through the animation of static layouts of program components. The aim of Look! is similar; however, Look! produces dynamic object diagrams, showing objects as they are created and destroyed and as they communicate, rather than animating static, structural views. Also, this system does not function by play-and-record--applications are animated as they execute. By pointing at screen objects, you can map the displayed objects or messages to the corresponding classes and functions and explore source code and data.

Dynamic Diagrams of C++ Programs

By embodying basic principles of sound engineering design directly in programming languages, object technology has standardized, at the architectural level, the components (program objects) that define a program both statically--classes and functions/methods--and dynamically--objects and messages. Likewise, relationships and communications between objects have become standardized.

In nonobject environments, the amount of design-level information that can be extracted from code is limited: Function-call trees can be produced, but these convey nothing about the state of data in the program; the only other important architectural information available is the module grouping of functions. By embedding design-level structures in the code, object technology allows the derivation of static, class-level information (as well as the usual function-call trees) and the automatic generation and animation of object-level diagrams that show the objects in the program as they are created and destroyed, as they communicate, and as their relationships change. Together with the source and machine level, the diagrammatic representation of object programs can be regarded as a third level of representation; see Table 1. At any point during execution of an object program, you can view the execution state at different levels, moving between them at will.


Table 1: Different ways to view programs.

Representation level Visual representation Granularity
Diagrammatic Method Call
Source
Expresult *ans= 0;
Expresult *left = leftD->eval();
Expresult *right = rightD->eval();
if (left && right) {
  ans = appluop(right,left);
  if (left) delete left;
  if (right) delete right;
}
return ans;
Source line
Machine
ov DX,seg D0
mov AX,offset D026h
les BX,6[BP]
mov ES:0Eh[BX],DX
mov ES:0Ch[BX],AX
inc word ptr _nautosD__4Auto
mov ES:byte ptr 010h[BX],1
xor AX,AX
Machine instruction

Visual Analogs of Object Programs

Object-oriented code lends itself naturally to visual representation. Programs consist of sets of objects that communicate with one another dynamically. You can represent objects, communications, and relationships using diagrams such as Figure 2, where objects are represented by circles, although icons representing object classes could also be used. Abstract objects (container, list, and the like) still have a visual analog. As well as using icons to differentiate classes of objects, the appearance of an object can be changed to indicate internal data changes or the current state of the object. In the latter case, you can show factors that affect the object as a whole, such as: if it is active, selected in the user interface, deleted while still active, or has been cross-referenced. The icon may also show physical aspects of the object such as memory allocation (static, heap, auto) or size (class-based).


Figure 2 Representing objects.


Grouping and displaying objects with different representations means that aspects of the state of the program can be rapidly comprehended and checked as the types and states of existing objects are visually broadcast.

Relationships between objects can be shown spatially (even if only a subset of relationships can be presented at any one time). The layout of objects is problematic, however, as the set of objects is dynamic, and the pattern of communication between objects generally cannot be predicted in advance. Animators (such as Look!) can incorporate different object-layout strategies or views:

Figure 1 is a sample creation-hierarchy view of a paint program. The program is structured to have a palette and toolbar on the main window, with tools being added to the toolbar. In this case, the creation hierarchy effectively shows the structure of the system because it is based on a containment organization.

At the object level, the unit of activity is the member-function call, so system activity is represented as member-function calls taking place between dynamic, iconic object representations. Each call is represented by dynamic arrows and message labels. Figure 2 shows a sequence of constructors as the system is initialized. The object graph changes dynamically as objects are created and destroyed.

Each object can have multiple relationships with other objects. The first-reference view uses one relationship as the basis of organizing the hierarchy. At any point, the other references to or from an object can be shown as an additional set of linking lines on an object diagram, acting as a visual cross reference between the objects. Pressing on a line displays the data member which actually stores that object reference.

Given the complexity of software systems, many two-dimensional views have to be used to describe a system. This applies to animation just as much as to design, so an animator must be able to display and animate multiple views of the program components simultaneously. These include object diagrams, message diagrams, class diagrams, and source views. I am focusing primarily on object diagrams because they are the most important and directly represent the dynamic state of the system. They are supplemented by the other views: Message traces show the details and history of object-to-object communications and allow replay of calling sequences; class diagrams show the usual static class relationships, although they can also be animated, showing the active class changing as objects communicate (see Figure 3).


Figure 3 The active class changes as objects communicate.


Understanding C++ Details

Although at the object level you gain an overall understanding of program operation, it is useful to directly link to the source level by synchronously animating source code and diagrammatic views. At the diagrammatic level, the system is stepped one function call at a time; at the source level, it is usual to step line by line. If source and diagrammatic views are simultaneously animated, then it is possible to see the sequence of calls (and the creation of temporary objects) caused by executing a single C++ line. Similarly, stepping by a message shows the lines executed for that call or return. In Figure 4, for example, the object view shows the implicit invocation of a constructor to convert a character string to a TdDate object as an assignment statement is stepped over.


Figure 4: (a) Source view; (b) object view.

(a) (b)


Object-level animation demonstrates fundamental C++ features such as construction and destruction ordering, different means of allocating objects, creation and destruction of temporary objects, invocation of type-conversion operators, resolution of calls among inherited classes, and base- and aggregate-object use. For example, the order of creation of the component parts of an object can be shown visually. The left-to-right ordering in Figure 5 indicates the sequence of initializing bases of the expand object. Last to be initialized is an aggregate object.


Figure 5 The sequence of initializing bases of the expand object.


The C++ puzzle in Example 1 (which originally appeared in "The C++ Puzzle" by R. Murray, C++ Report, November/December 1992) illustrates how you can visually follow a program. Ignoring for the moment questions of C++ style (passing the node by value is unnecessary, and why have a single Node class for three Node types?), the bottom line is that nodeA does not get printed. Instead, the unary constructor for node--Node(Node &)--is invoked, because it has the form of a C++ copy constructor. The unary constructor doesn't copy, however; it creates a new node and attaches the one we passed in to it. So now you are trying to print a temporary unary node instead of a copy of nodeA. Just looking at the text, this problem may be a bit difficult to spot, but running the example through the animator shows nodeA being created, followed by a temporary CnodeA object. When CnodeA is created, nodeA becomes referenced from CnodeA, and this reference change is automatically detected by the animator and shown by reparenting nodeA below CnodeA; see Figure 6. The existence of the CnodeA/nodeA reference clearly indicates the problem; checking the message trace confirms it.


Example 1: C++ puzzle illustrating how you can visually follow a program.

class Node
{
  ...
public:
  Node();
  Node (Node &);          // Unary
  Node (Node &, Node &);  // Binary
};
void display_node()
{
  Node nodeA;
  Display   display;
  display->print(nodeA);
}


Figure 6 Tree view.


Besides directly showing incorrect structuring and references, object-level animation detects various memory-usage errors. To animate C++ without requiring source changes, a program must be monitored at the object-code level and a considerable amount of information about the program must be maintained to identify objects and member-function calls. This monitoring also yields many common C++ bugs--objects being deleted while still active, use of invalid object pointers or references, and the passing of invalid pointers or references--which are automatically detected as they occur.

Filtering

As critics of program animation often (correctly) point out, systems of significant size often have too many objects and messages for the entire operation of the system to be animated. Consequently, you may want to view only selected classes of objects, or objects created by certain other objects, or objects created after a certain message sequence. Animation systems must therefore provide facilities for filtering the display of system activity.

We have found that filtering should focus on the exclusion of sections of the program. It appears more natural to gradually remove irrelevant activity rather than having to state initially what you want to see. Ideally, all filtering could be done visually by pointing at the things to leave out. Static filtering of this type can exclude (and include) classes, functions, and modules simply by pointing at them on a static view. In the dynamic object views, pointing at an object allows the corresponding class to be included or excluded; similarly, pointing at a message label allows the function to be excluded or included.

However, in the most general case there is a need to be able to exclude sections of program activity; for example, to show only calls from objectA to objectB, or every call from the object which created objectA. To do this, you must be able to make statements about the dynamic structure of the program; this can be done by defining the set of messages (member-function calls) to animate. Each call has a sending and a receiving object, and we can reason about the attributes of these objects and the function, in order to decide whether the message is to be included or excluded. For instance, Example 2(a) doesn't show calls to functions called paint in objectA, while Example 2(b) stops every time a method is called on a Persistent object. Example 2(c) only shows object construction, and Example 2(d) shows all activity until any object created by the display_manager makes a call, and then excludes all further activity.


Example 2: Filtering program execution.

(a)  Filter remove_paints
     (to.name == objectA && function.name == paint)
         { exclude; }

(b)  Filter stop_on_store (to.class.baseclass.name == Persistent)
         { stop; }

(c)  Filter show_construction(function.type != constructor)
         { exclude; }

(d)  Filter until_suppliers (from.parent.name == display_manager)
         {
           add exclude_all;
           remove until_suppliers;
         }
     Filter exclude_all() { exclude; }

Dynamic filters are expressed in an extension of C++ expression syntax that allows a wide range of statements to be made about a metamodel describing the inheritance and creation structure of any C++ program. Dynamic filters can be entered using a graphical editor, or textually, as in
Example 2.

Conclusions

With some exceptions, the current interest in C++ focuses on detailed syntactic features, not opportunities to exploit object technology to construct large, maintainable systems. Object technology creates systems as sets of intercommunicating objects, a structure which has an intuitive and powerful visual representation. Exploiting our cognitive abilities by representing program execution with visual analogs allows you to view and interact with programs in intuitive ways. This has only recently become practical because of the standardization of program structure brought about by object technology. Animation is practical and effective. As the capabilities of GUIs increase (and systems become more standardized), it will become possible to provide an increasing amount of relevant information.


Copyright © 1994, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.