The Binary Book

Sample Chapter

By Steven Roman

Version 1.0.0

An electronic book from

The Roman Press, Inc.

www.romanpress.com

Copyright Ó 2000 by The Roman Press, Inc.

All Rights reserved.

This book is for your own personal use only.

You may print selected portions of the book for your own use only.

You may not distribute this book to anyone else in whole or in part.


About the Author

Dr. Steven Roman is an Emeritus Professor of Mathematics at the California State University, Fullerton. He has taught at a number of other universities, including MIT, the University of California at Santa Barbara, and the University of South Florida. He received his B.A. degree from the University of California at Los Angeles and his Ph.D. in mathematics (specializing in combinatorics and graph theory) from the University of Washington in Seattle.

Dr. Roman has written 35 books, including 10 books on mathematics, ranging from a series of three books on college algebra to graduate level and research level books on coding theory and various areas of abstract algebra. He has also written a series of 15 small books entitled Modules in Mathematics, designed for the general college-level liberal arts student.

Besides his mathematics books, Dr. Roman has written several books on computing, to wit:

  • Access Database Design & Programming, Second Edition, O’Reilly & Associates

  • Writing Word Macros, O’Reilly & Associates

  • Writing Excel Macros, O’Reilly & Associates

  • Developing Visual Basic Add-Ins, O’Reilly & Associates

  • Win32 API Programming with Visual Basic, O’Reilly & Associates

  • Concepts of Object-Oriented Programming with Visual Basic, Springer-Verlag

  • Understanding Personal Computer Hardware, Springer-Verlag

Dr. Roman has also written several Windows applications, including Object Model Browser. When not writing books or software, he does programming for companies on a consulting basis.

Dr. Roman’s hobbies include making period furniture, copying Van Gogh paintings and listening to classical music.


Introduction

Being a good programmer requires both skill and knowledge. Certainly, a programmer needs to have a solid understanding of his (or her) preferred programming languages, as well as the subject matter of the applications under development. But this is not enough. There is more to being an effective programmer.

This book delves into three areas of importance to programmers—areas that are all too often neglected.

  • A look at binary, octal and hexadecimal number formats and their use by the computer. This includes converting between binary, octal, decimal and hexadecimal bases.

  • A look at how to manipulate the individual bits in a binary string, that is, a bit flag.

  • A look at data types, with examples drawn from both Visual Basic and Visual C++. This includes a look at signed and unsigned numerical data types, floating data types, strings, pointers and structures.

  • A look at the basic principles of elementary logic. In particular, a look at the meaning of the various logical operators (AND, OR, NOT, XOR and so on).


Part II - Data Types


Chapter 4 - Preliminaries


Variables

A variable can be defined as an object that has the following six properties:

  • name

  • address

  • type

  • value

  • lifetime

  • scope

Let us discuss these properties.


Name

A variable’s name is used to identify the variable in code. Of course, each programming language has its own rules as to the type of string that can be used as a variable name.


Address

Every variable must be assigned a memory address, which is the location at which the variable’s value is stored. In some circumstances, this address may change during the lifetime of the variable. Visual C++ has the address-of operator (&) that returns the address associated with a variable.

On the other hand, VB attempts to shield the programmer from addresses. Nevertheless, it does support the undocumented function VarPtr that returns the address of its argument. This function is left over from the days of QuickBasic. The QuickBasic documentation says that VarPtr returns the offset of a variable and VarSeg returns the segment. The days of segmented addressing are mercifully gone, but VarPtr seems to have survived. It now simply returns the address of a variable, that is

VarPtr(var)

is the address of the variable var.

The address of a variable is sometimes referred to as the variable’s l-value because it is the memory address that is really required when the variable appears on the left side of an assignment statement. For instance, the statement

i = 5

is really saying "place the value 5 at the memory address of the variable i". (By contrast, the value of a variable is called the r-value.)


Type

The type of a variable (also called its data type) determines the possible values that the variable can assume. We will spend considerable time discussing data types later in the book, where we will give a careful definition of the term. One thing to keep in mind now is that not only does a data type have an associated set of possible values (and set of possible operations on those values) but it also must define a way to represent those values in computer memory.

For instance, in VB, the set of possible values for the Integer data type is the set of integers in the range –32,768 to 32,767. In addition, these integers are represented in computer memory in the so-called two’s complement format, which we will discuss in a later chapter. The point we want to make here is that this is part of the specification of the Integer data type.


Value

The value of a variable is the contents of the memory location at the address of the variable. This is also sometimes referred to as the r-value of the variable, since it is what really appears on the right side of an assignment statement. For instance, in the code

int i;

int j;

i = 5;

j = i;

the final statement can be read "assign the value of i to the memory at the address of j".


Variable Scope

Variables (and constants) have a scope, which indicates where in the program the variable is recognized, that is, where it can be referred to in code. For instance, a variable that is defined (without any special qualification) in a Visual Basic procedure or a Visual C++ code block (that is, a block of code delimited by braces) is referred to as a local variable. Its scope consists of the procedure or block in which it is declared. One of the advantages of local variables is that the same name can be used in different procedures (or blocks) without conflict, since each variable is visible only to its own procedure (or block).

In Visual Basic, a variable can have one of three different scopes:

  • Procedure-level scope (local)

  • Module-level private scope

  • Module-level public scope

A module-level variable is one that is declared in the declarations section of a code module (standard, class or form).

A module-level public variable (or constant) is available to all procedures in all of the modules in the project, not just the module in which it is declared, whereas a module-level private variable (or constant) is available only to the procedures in the module in which it was declared.

Public variables are also referred to as global variables, but this descriptive term is not de rigueur.

In Visual C++, a variable can have a variety of scopes. Here is a brief description. More information can be found in the Microsoft documentation.

  • Block-level scope (local): A variable defined within a code block, that is, a block of code that is delimited by braces. Such a variable is visible only within the block in which it is defined. For instance, the VC++ code

{

int i = 5;

}

cout << i << endl;

will produce the compile error "undeclared identifier" on the line that begins with cout because the variable i is visible only within its code block.

  • File scope: Any variable (or constant) declared outside all code blocks and all classes is said to have file scope and is visible anywhere in the file after the point at which it is declared. Nonstatic file scope variables are also called global variables. (We will discuss static variables a bit later.)

  • Class scope: When a member variable is defined within a class, as in the following example

class Rectangle

{

public:

int height;

int width;

};

the variables width and height are visible within the class code. In addition, they are visible to other portions of the program through the use of the member-selection operators, as in

Rectangle r;

r.height = 5;

r.width = 7;

cout << r.height << endl;

Variables such as height and width are said to have class scope.

  • Prototype scope: In the function prototype

char *strcpy( char *szDest, const char *szSource );

the variables szDest and szSource are visible only within the prototype itself. These variables are said to have prototype scope.


Variable Lifetime

Variables also have a lifetime. The difference between lifetime and scope is quite simple: Lifetime refers to how long (or when) the variable is valid (that is, exists) whereas scope refers to where in the program the variable is recognized by the code.

To illustrate the difference, consider the following procedure:

Sub ProcedureA()

Dim LocalVar As Integer

LocalVar = 0

Call ProcedureB

LocalVar = 1

End Sub

Note that LocalVar is a local variable. When the line

Call ProcedureB

is executed, execution switches to ProcedureB. While the lines of ProcedureB are being executed, the variable LocalVar is out of scope, since it is local to ProcedureA. But it is still valid! In other words, the variable still exists and has a value. It is simply not accessible to the code in ProcedureB. In fact, ProcedureB could also have a local variable named LocalVar, which would have nothing to do with the variable of the same name in ProcedureA.

Once ProcedureB has completed, execution continues in ProcedureA with the line

LocalVar = 1

which is a valid instruction, since the variable LocalVar is back in scope.

Thus, the lifetime of the local variable LocalVar extends from the moment that ProcedureA is entered to the moment that it is terminated, including the period during which ProcedureB is being executed as a result of the call to this procedure, even though during that period, LocalVar is out of scope.

Incidentally, you may notice that the Microsoft help files occasionally get the notions of scope and visibility mixed up a bit. For instance, here is a quote from the MSDN documentation

C++ names can be used only in certain regions of a program. This area is called the "scope" of the name. Scope determines the "lifetime" of a name that does not denote an object of static extent.


Static Variables

To repeat, a variable may go in and out of scope and yet remain valid during that time, that is, retain a value during that time. However, once the lifetime of a variable expires, the variable is destroyed and its value is lost. It is the lifetime that determines the existence of a variable; its scope determines its visibility.

Thus, consider the following procedures:

Sub ProcedureA()

Call ProcedureB

Call ProcedureB

Call ProcedureB

Call ProcedureB

Call ProcedureB

End Sub

Sub ProcedureB()

Dim x As Integer

x = 5

. . .

End Sub

When ProcedureA is executed, it simply calls ProcedureB five times. Each time ProcedureB is called, the local variable x is created anew and destroyed at the end of that call. Thus, x is created and destroyed five times.

Normally, this is just want we want. However, there are times when we would like the lifetime of a local variable to persist longer than the lifetime of the procedure in which it is declared. As an example, we may want a procedure to do something special the first time it is called, but not subsequent times.

A static variable is a local variable whose lifetime is the lifetime of the entire program. The following VC++ code shows how one might use a static variable:

void test();

void main(void)

{

test();

test();

test();

}

void test()

{

static bool bFirstTime = true;

if ( bFirstTime )

{

cout << "first time" << endl;

bFirstTime = false;

}

else

cout << "not first time" << endl;

}

The output of this code is

first time

not first time

not first time

In VB, we can do something similar:

Sub test()

Static bNotFirstTime As Boolean

If bNotFirstTime = False Then

Debug.Print "first time"

bNotFirstTime = True

Else

Debug.Print "not first time"

End If

End Sub

Note that, unlike VC++, in VB, there is no way to initialize a static variable, so a static Boolean variable will be given its default initial value of False. The way around this is to use the variable bNotFirstTime, which we want to have initial value False!

It may have occurred to you that we could accomplish the same effect by using a global variable to keep a record of whether or not the procedure has been called, instead of a static local variable. However, it is considered better programming style to use the most restrictive scope possible which, in this case, is a local variable with an "extended" lifetime. This helps prevent accidental alteration of the variable in other portions of the code. (Remember that this code may be part of a much larger code module, with a lot of things going on.)


Pointers

Simply put, a pointer is a memory address. Under 32-bit Windows, all memory addresses are 32-bits long. A pointer variable (often just called a pointer) is a variable of type pointer, that is, a variable that the compiler interprets as holding an address. Figure 1 shows a pointer variable.

Figure 1 – A pointer variable

In this figure, Var is a variable of some type or other (integer, long, character, whatever). Its contents are yy...y and its address is bbbb. The variable pVar is a pointer variable. It contains the address of Var and so, like all pointer variables, has length 32 bits. We say that pVar points to Var and that Var is the target of the pointer. If Var has type Integer, for example, we would say that pVar is an integer pointer.

Pointers and pointer variables are extremely powerful objects and Win32 (the programming arm of the Windows operating system) makes extensive use of them. Accordingly, VC++ fully supports pointers and pointer operations. An example of the influence of pointers on the VC++ language can be seen in pointer arithmetic, which can seem quite strange to the uninitiated.

The following code (whose syntax we will discuss in more detail later), declares an integer pointer pi and then prints the value of both pi and pi+1:

int i = 1; // declare integer and initialize

int *pi; // declare pointer to integer

pi = &i; // set pointer to point to integer (& is VC++ AddressOf operator)

cout << pi << " / " << pi+1; // print value of pi and pi+1

Surprisingly, the output is

0x0012FF78 / 0x0012FF7C

(Recall that 0x is the prefix that VC++ uses to indicate hex). Note that pi + 1 is actually 4 larger than pi!

The reason is that the VC++ compiler understands that pi points to (holds the address of) a 4-byte integer and it understands that the only reason to add 1 to a pointer is to point to the next item (in this case 4-byte integer) in memory. In this case, the address of this item is 4 greater than the address of the current integer. Hence, the compiler adds 4 (not 1) to the value of the pointer! You can probably see that, while this may seem strange at first, it can be very useful, because it allows us to simply add 1 to any pointer to point to the next item of whatever the target type is: integer, long, character, etc.

Unlike VC++, Visual Basic makes every effort to hide pointers from the programmer. The reason is that programming with pointers can be dangerous in the sense that an accidental (or deliberate) misuse of a pointer can easily result in an attempt to access protected memory. For instance, it is a relatively common mistake to forget to initialize a pointer variable, since this requires defining a target variable, as we did in the previous code. If we forget to initialize a pointer, the result is a null pointer, that is, a pointer that points to the byte at address 0. This is a protected byte and any attempt to access it will cause a memory access exception (a General Protection Fault).

This notwithstanding, as we will see, there are some tricks that make it possible to deal with pointers in Visual Basic.

Declaring a pointer in VC++ is easy. It is done using the syntax

targetdatatype *pointervariable;

For instance, to declare a pointer to an integer, we write

int *pi;

It is possible to declare a pointer variable without knowing the target data type. This is done by writing

void *pWhatever;

Incidentally, as we will see, VC++ is replete with synonyms for data types. Thus, you may also see this declaration in the equivalent form

LPVOID pWhatever;

where LPVOID stands for long pointer to a void.


By Indirections Find Directions Out

In VC++, the asterisk (*) is known as the indirection operator. It is used both to declare a pointer variable (as above) and to get at the value pointed to by a pointer variable, that is, the value of the target variable. For instance, the following code

int i; // declare integer

int *pi; // declare pointer

pi = &i; // set pointer to address of integer

i = 5; // place value in integer variable

// output to console

cout << "Pointer: " << pi << " Target: " << *pi;

produces the following output (to a DOS window)

Pointer: 0x0012FF78 Target: 5

Note that in the last line, the expression *pi means "that which is pointed to by the pointer pi" or more succinctly "the target of pi."

Note also that the & operator, called the address-of operator, returns the address of its operand. Thus

&var

is the address of the variable var. This operator is very useful for filling in pointer variables.


Pointers in Visual Basic

The indirection and address-of operators are the key to using pointers in VC++. As it happens, the address-of operator has an undocumented counterpart in Visual Basic and the indirection operator can be faked without too much trouble through the use of the API function CopyMemory.

As mentioned earlier, Visual Basic has an undocumented function called VarPtr, left over from the days of QuickBasic. The QuickBasic documentation says that VarPtr returns the offset of a variable and VarSeg returns the segment. The days of segmented addressing are mercifully gone, but VarPtr seems to have survived. It now simply returns the address of a variable, that is

VarPtr(var)

is the address of the variable var.

To do indirection, we need some way to retrieve the target of a pointer. The simplest way to do this is to use the CopyMemory API function, which copies bytes of memory from one address to another address. Let us discuss the CopyMemory function in some detail.


CopyMemory - A VB Hackers Dream

The purpose of the Win32 API CopyMemory function is simply to copy a block of memory byte-by-byte from one memory address to another. This opens up a whole new set of possibilities in Visual Basic, since VB does not have this sort of capability, except in the rather restricted form of LSet, and even then, the documentation recommends against using LSet for this purpose. (Of course, VC++ programmers can also use CopyMemory, but often a simple recasting is all that is necessary to peek at the bytes in variable.)

CopyMemory is defined in Microsoft’s documentation (MSDN) as

VOID CopyMemory(

PVOID Destination, // pointer to address of copy destination

CONST VOID *Source, // pointer to address of block to copy

DWORD Length // size, in bytes, of block to copy

);

The keyword CONST simply means that the function CopyMemory guarantees not to make any changes to the argument Source. Since PVOID is a synonym for VOID *, the two parameters Source and Destination have the same data type—a pointer to a target variable of unknown type.

The simplest VB declaration for CopyMemory is

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _

lpDest As Any, _

lpSource As Any, _

ByVal cbCopy As Long)

In this case, lpDest is the address of the first byte of the destination memory, lpSource is the address of the first byte of the source memory and cbCopy is the number of bytes to copy (the cb in cbCopy stands for count of bytes).


A Simple Example

Let us consider an example. The following code

Dim lng As Long

Dim bArray(1 To 4) As Byte

lng = &H4030201

CopyMemory bArray(1), lng, 4

For i = 1 To 4

Debug.Print Hex(bArray(i));

Next

copies the long variable lng, byte-by-byte, to the 4-byte byte array and then prints those bytes. The output is

1 2 3 4

You might have expected that the output would be

4 3 2 1

However, the fact is that the bytes of a long are stored in memory with the lowest order byte stored at the lowest address, as shown in Figure 1.

Figure 1 – The long value &H4030201 stored in memory at address Addr

As discussed earlier in the book, this method of storing words in memory is referred to as little endian.


O'Reilly RTF Template for Microsoft Word

A More Interesting Example

CopyMemory is very useful for exploring beneath the hood of VB. To illustrate, let’s try a little experiment with strings. Given a VB string, we allocate a byte array, move the bytes from the string to that array and then examine those bytes.

Here is the code. We will discuss the reason for the use of ByVal with the source string in the chapter on strings, so don’t worry about that now.

Dim sString As String

Dim aBytes(1 To 20) As Byte

Dim i As Integer

sString = "help"

CopyMemory aBytes(1), ByVal sString, LenB(sString)

' Print bytes

For i = 1 To 10

Debug.Print aBytes(i);

Next

The output from this program is

104 101 108 112 0 0 0 0 0 0

The string officially ends with the first null character (ASCII 0). Note that it appears that the string is in ANSI format—one byte per character. But wait.

Another approach to getting the bytes from the VB string is to declare a long variable and point it to the same character array as the string. We will go into the details of this in the chapter on strings, but here is the code.

Dim sString As String

Dim aBytes(1 To 20) As Byte

Dim i As Integer

Dim lng As Long

sString = "help"

' Get contents of BSTR variable

CopyMemory lng, sString, 4

' Now copy as array

CopyMemory aBytes(1), ByVal lng, LenB(sString)

' Print bytes

For i = 1 To 10

Debug.Print aBytes(i);

Next

This time, the output is

104 0 101 0 108 0 112 0 0 0

which is definitely Unicode (two bytes per character), which seem to contradict the previous output.

As we will see, the fact is that when we pass a string to an external function, such as CopyMemory, VB translates the string from Unicode to ANSI. This is why the first output is in ANSI format. However, we were able to bypass VB’s translation in the second call to CopyMemory by avoiding VB strings altogether. This is why the output is Unicode.

We will explain this in more detail in the chapter on strings. For now, we wanted simply to point out how useful CopyMemory can be for peering into the inner workings of VB.