We use cookies to distinguish you from other users and to provide you with a better experience on our websites. Close this message to accept cookies or find out how to manage your cookie settings.
To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure [email protected]
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
This chapter introduces the general process model on which the developed case studies were based. It commences with an overview of software development phases and general process models as a preamble to introducing the bridge process model based on which the case studies in the forthcoming chapters were developed.
AN OVERVIEW OF PROCESS MODELS
Several process models were discussed and developed to provide a set of activities that permits the development of software systems from user requirements. Almost all processes follow the development of software through the following phases: analysis, design, and implementation, in addition to system testing.
Analysis is the phase during which user requirements are determined and requirement specifications are documented. User requirements are typically collected in a language that is familiar to the user, where a user in this context could mean a real user, the company, or a vendor that requests the software to be developed to automate certain services. The requirements describe what the system is requested to deliver as automated services. Requirements are documented in a specification language that is understood by the designers and developers and that steps away from the user, creating, more or less, a gap between the language and requirements expressed by the user and language and tools applied by analysts, designers, and implementers. The output of this phase is a requirement specification document.
The design phase follows analysis. During design a system designer relies on system specifications reached during the analysis phase to lay out the design of the system.
This case study was developed following the bridge process with a simple modification. The description of user requirements is rather simple but it is complemented with a detailed description of all stages that the application goes through and it proves to be useful in advanced phases of the target system design. The initial use case model is very simple, whereas the second version of the use case model exhibit “uses” relations that are worth examining. The object model exhibits inheritance and aggregation relations. Details of traceability and other details that lead to formation of the models are left as a challenge to the reader in the exercise section at the end of the chapter.
INCEPTION
USER REQUIREMENTS
Math tutor is an application that educates and trains students in lower level classes in mathematics. The application supports two levels (medium and high) of competency, where each level contains several stages that students have to complete to move across levels. To move from one level to another, a student has to achieve a certain score. Scores are gained during lessons. Each lesson consists of two parts: practice and an evaluation quiz. The student collects his/her score from the first part of each lesson. Each successful level is followed by a game or song in order to motivate the student.
Description 6-1. Tutor user requirements
Detailed Description of Stages
When starting the application, the student has two choices:
1. The user chooses to take consecutive lessons. The user will start from the lowest level (level 1) and go consecutively through all the lessons of each of levels 1 and 2. In this case, the user will not receive any prize.
This part introduces three case studies taken from different problem domains. The first case study is on simulation, the second is on education, and the third on product distribution. Each could be used as a model to build applications in similar domain by applying the Bridge process. The case studies present models reached leaving out the details of deriving these models as exercises to the reader. Each chapter concludes with exercises that guide the reader in this direction.
Classification of the causes of memory leaks. Tracing memory leaks in C programs using location reporting and allocation/deallocation information-gathering versions of the C allocators and deallocators. Tracing memory leaks in C++ programs: overloading the operators new and delete and the problems it causes. Techniques for location tracing. Counting objects in C++. Smart pointers as a remedy for memory leaks caused by the undetermined ownership problem.
As mentioned previously, I do not like the terms “memory leaks” or “leaking memory”. They somehow put the onus on memory, as if it were the memory's inadequacy that caused the problem. Every time I hear a project manager or a student explain in a grave tone of voice that “the project is delayed because we have memory leaking”, I feel like retorting “OK, find a better memory that doesn't leak”. In truth, it's not the memory but rather the program that is inadequate. We should be talking about leaking programs, not about leaking memory. In this chapter we will classify the most common problems leading to memory leaks and discuss how to identify and locate them. We will start with trivial and obvious problems and proceed to more subtle ones that are harder to deal with.
The first class of memory leaks is called orphaned allocation and is characterized by allocation of a memory segment whose address is not preserved for later deallocation.
Transformation of a source file to a load (executable) module. Why we can and do discuss source programs and their behavior as if they were executing somewhere in memory in their source form. Concepts of static memory allocation, dynamic memory allocation, program address space, and program system stack.
It is useful and practical to discuss the behavior (often referred to as the semantics) of a computer program written in a high-level language like C or C++ as if it were executing in computer memory in its source form. For instance, the semantics of the statement x = x+1 might be described as “the value of the variable x is incremented by 1”, yet nothing could be farther from the truth because the program in its source form is a simple ASCII text file sitting quietly somewhere on a disk doing nothing. On the other hand, speaking conceptually, this is exactly what happens to the variable x when the program executes - although, to confuse matters even more, there is no variable x to speak of when the program is running. In order to understand all of this, we must discuss the process of compilation, linking, loading, and execution of programs. Most of the facts discussed in this chapter can be found in various books and texts dealing with compilation and compilers, operating systems, and computer architecture.
Both C and C++ belong to a family of high-level symbolic languages, meaning that certain entities in such programs can be referenced by their names (symbols).
Fundamentals, advantages, and disadvantages of linked data structures. Moving a linked data structure in memory, or to/from a disk, or transmitting it across a communication channel - techniques of compaction and serialization. Memory allocation from a specific arena.
Linked data structures are intimately related to memory, where they are created, located, and processed. Naturally for this book, this relationship with memory is our principal focus in this chapter. It is not our intention to provide a comprehensive presentation of linked data structures in C or C++ and their applications. There are many excellent books on the topic of algorithms and data structures, particularly in C and C++.
Linked data structures and their applications are one of the great successes of the early decades of computer science. The reader can easily imagine how useful software would be if it worked solely with numbers and arrays. But how then would we model and program such things as lists, graphs, stacks, queues, charts, diagrams, and many other abstract notions that are needed to deal with today's complex programming tasks?
Philosophically, a linked approach to data structures is used for any of the following reasons: the data structure must be created dynamically; each part of it is created at a different time; the mutual relations of the parts change in time. Sometimes links are used as a “physical” implementation of “logical relations” (the best example would be databases and the use of foreign keys as “links”).
System stack, activation frame, activation frame as the storage for local auto objects and for function arguments. Passing arguments by value as opposed to by reference. Calling sequence. Recursion and its relation to activation frames and the system stack. The price of recursion.
Any program of even modest complexity must be modularized for many reasons: readability, testability, validation, extendibility, maintainability, and many others, all outside the scope of this book. However, we assume that the reader understands that any reasonable programming language must allow for some modularization; C and C++ are no exceptions. Besides modularization on the level of source files, virtually the only modularization feature of C consists of structuring a program into separate functions. The object orientation of C++ allows for a more complex and far better-controlled modularization.
A C/C++ function is a programming module with a precisely defined interface that consists of the function's header, which specifies the function's name, the data types of the arguments (the input to the function - though it is possible for a function not to have any arguments), and the data type of a single return value (the output of the function; again, it is possible for a function to return nothing at all). Let us observe that computer scientists prefer to speak of procedures (if no value is returned) and functions (if a value is returned and the execution has no “side effects”, meaning that it does not modify any data not local to the function).
The motivation for this book came from years of observing computer science students at universities as well as professional programmers working in software development. I had come to the conclusion that there seemed to be a gap in their understanding of programming. They usually understood the syntax of the programming language they were using and had a reasonable grasp of such topics as algorithms and data structures. However, a program is not executed in a vacuum; it is executed in computer memory. This simple fact exerts a powerful influence on the actual behavior of the program - or, expressed more precisely, a subtle yet powerful influence on the semantics of the particular programming language. I had observed that many students and programmers did not fully understand how memory affected the behavior of the C and C++ programs they were designing. This book is an attempt to fill this gap and provide students and programmers alike with a text that is focused on this topic.
In a typical computer science curriculum, it is expected that students take courses in computer architecture, operating systems, compilers, and principles of programming languages - courses that should provide them with a “model” of how memory matters in the behavior of programs.
However, not all students end up taking all these courses, and even if they do, they may not take them in the right order. Often the courses are presented in a disjointed way, making it difficult for students to forge a unified view of how memory affects the execution of programs.
Variables as “data containers” with names. Values as data - simple (innate or elementary) data, structures, and objects. Referencing variables through pointers. Unnamed “data containers” and their referencing through pointers. The dual role of pointers as address holders and binary code “interpreters”. Various interpretations of the contents of a piece of memory. Pointer arithmetic. Why C/C++ cannot be interpreted in a platform-free manner like Java can. Why C/C++ cannot have a garbage collector.
During the execution of a program, a variable of the program corresponds to a location in memory, and the address of that location replaces all symbolic references to the variable in the load module. This is one of the important facts touched upon in Chapter 2 when we discussed why we can behave as if the program in its source form executes in the memory. In this chapter we will refine this notion and discuss its consequences.
The idea of variable as “data container” is very natural. In its crudest form we can imagine a variable to be a box, and whatever is in the box is the value of that variable. If we want to evaluate the variable (i.e., find its value), all we need do is look in the box and see what is in there; when we want to store something in the variable, we simply put it into the box. In fact, this crude notion is not that far from reality.
Fundamentals of dynamic allocation and deallocation of memory: free store (system heap); per-process memory manager; C memory allocators malloc(), calloc(), and realloc(); and C deallocator free(). How to handle memory allocation/deallocation errors.
In previous chapters we have mentioned dynamic allocation of memory several times. In Chapter 2 we had quite a detailed look at the static allocation when we discussed the process of compilation, linking, loading, and execution of a program. We mentioned dynamic allocation from a general point of view or, to be more precise, from the operating system point of view.
The reader should be comfortable with the idea that a running program is allocated several segments - not necessarily contiguous - of memory to which its address space is mapped during program execution. These segments together constitute “the program's memory space”, or the “memory it owns”, where all the program's instructions and data needed for the execution are stored and accessible. It is obvious that many programs need to increase their memory during their execution; for instance, they might need to store more data or even more instructions.
As mentioned at the end of Chapter 2, it would be more precise to talk about a process rather than a running program. Modern operating systems like UNIX thus have a process memory manager, software that is responsible for providing additional allocation of memory to its process and for deallocation when the memory is no longer needed.
Static one-dimensional arrays and their representation as pointers. Array indexing as indirection. Why an array index range check cannot be performed in C/C++. The price of run-time array index range checking; “compile-time checking” versus “run-time checking” philosophies. Passing static one-dimensional arrays as function arguments. Definition versus declaration of one-dimensional arrays. Dynamic one-dimensional arrays. Strings as static or dynamic one-dimensional char arrays terminated with NULL. How to add a custom-made run-time index range checker in C++.
The most frequently used composite data structures in programming are arrays. Since their use is so ubiquitous, they are provided as almost built-in data types in C/C++. Unfortunately, the emphasis is on “almost” and not on “built-in”. On the one hand, “almost” here means that you need not provide any programmatic means of creating (building) and indexing the array and that any C/C++ compiler will provide both automatically. The array definition/declaration and access to the array items through indexing are thus integral parts of the C/C++ language. On the other hand, “almost” also means there is no innate data type array in C/C++ that is treated in its own special way, so each array is represented by an appropriate pointer pointing to an appropriate storage according to appropriate conventions. This has some unexpected consequences for “index out of range” run-time errors and for passing array arguments to functions.
The fundamental notion of an array is just an extension of the basic model of computer memory: an array of bytes accessible through indexes (their addresses).