Binding

Reusable software is created with points of variation, such that the software can be bound together with another set of components in a different context. In this document, I explore the different ways that a function name can be bound to (associated with) its implementation. A similar binding may ocurr for data, but there tends to be much less global data ... right?

Dynamic Binding

Dynamic binding is the association of a functions implementation with its name that happens during the execution of a program at run-time.

Object oriented programmers are usually familiar with the term "late binding", which is used to describe the association of an operation name to the run-time address of the operation. C++ virtual functions and callback operations are a form of "late binding."

Late binding is useful in those parts of a system that must be varied as the program executes. Late binding is a dynamic form. There also exist static forms of binding.

Static Binding

Static binding is when a function name is associated with its implementation before the program is executed during the build process.

One familiar use of static binding is to bind the source code of a program to a particular instruction set, and thus a particular type of processor. This kind of static binding is the job of a compiler. Typically, a C++ compiler transforms the C++ source code into assembly language source code for a particular processor type, and that assembly language source code is subsequently translated to a binary representation by an assembler.

Static Linker Binding

Binding is also done by another common element of the C++ toolchain known as the linker. The linker builds a single object module from a list of object modules.  Each object module consists of a set of symbols that it defines, and may also contain a set of symbols which it needs to have resolved. Each symbol represents the address of a code or data object in the runtime environment.

The binding provided by the linker allows for exactly one definition of a given symbol between the set of object files that constitute a program. For example, there can be only one address for the code that implements the function foo() . All clients of foo() will execute the code at that address.

From a binding perspective, there may be any number of object files which implement a given function (e.g. foo()) , but only one of those object files may be linked with the final executable program.  ( An attempt to link more than one such object file will result in a "multiply defined symbol" linker error.)

This form of binding is implemented in the BENV primarily by entries in a project's "libdirs.b" file. However, static linker binding is also perfomed in portions of the "overrides.b" file, the "firstobjects.b" file, the "extraobjects.b" file, and sometimes within the "targetos" and "linkfile" specifications.

Static Text Binding

Other forms of binding are those done by the C++ preprocessor. The C++ preprocessor has two mechanisms that are used for binding 1) text substitution as performed by the definition of macros, and 2) the include file mechanism.

Macros

In case you don't already know by now, I am of the opinion that preprocessor macros are basically evil. To be fair, it is not the macro mechanism itself that is evil, but rather the abuse it has sufferred at the hands of programmers. Prior to the enum and inline mechanisms, there were many valid uses of this facility, however, there are few good excuses to use/misuse macros in today's world.

The OSCL and BENV make little use of the preprocessor macro facility, with the exception of include file gards to prevent recursive include problems.

Include Paths and Files

The "include" mechanism of the preprocessor, however, is important because it allows C++ programmers to separate interface and implementation. Header files are best used to contain interface, type, and constant definitions, but header files may also contain short program definitions, that for performance reasons are declared as "inline."

It is these "inline" definitions that are another type of binding performed by the preprocessor. In the usual build sequence for C++ source files, the source file is first preprocessed, and then compiled. These steps are generally done with a single invocation of the "compiler". One of the most standard command line options to the compiler is the "-I" option, which allows the programmer to specify include file paths. Within the source code, "#include" directives specify files with relative (rather than absolute) path names. Theefore, the include file paths supplied on the command line allow the programmer to vary which set of include files to be used to compile a given file. This binding I will refer to is include path binding.

The BENV makefile variable LIB_INC_ROOTS is defined in "overrides.b" and used to specify the include paths for this type of binding. One common use in the OSCL is to specify the processor specific inlined methods for operations such as those that disable and restore the processor interrupt enable.