Tech Talk

See The Errors? (Part 2)

By Reg Charney

Last month, I published an article critiquing an MSDN sample. I also proposed a solution. After presenting a more expanded version of that article at our monthly ACCU meeting, Al Bauer, sent me this solution which I believe has a lot of value, so I have presented here. [I have made some minor changes to fit this page format—Ed.]

“Here is my [Al’s] version of the sample program of the ACCU meeting of Oct. 10. I had some trouble finding enough include files to get all types defined; hence my own ULONG and using char instead of TCHAR.

Generally, I have substituted declarations and data for algorithms.

Feedback? Specifically, I was interested in your (Reg) comment that a nested if (like mine) compiles to less efficient object code than using goto's.

#include <stdlib.h>
#include <stdio.h>
#include "toolhelp.h"
#include <commdlg.h>

typedef unsigned long ULONG;

BOOL OpenTheFile (void)
{
 
typedef enum {ekOK, ekFDia, ekOpen, ekFSiz, ekFZro, ekGAlo, 
                ekRdFi, ekRdZr, ekProc} terrKind;
 
typedef char* terrMsgs[ekProc+1];
 
terrKind errKind = ekOK;
 
static terrMsgs errMsgs = { "No Error", "File Not Fnd", 
                              "Cant Open","Invalid FSize", 
                              "File Empty", "Alloc Error", 
                             
"File Read Err", "No Bytes Read",
                             
"Process Err" };
 
HANDLE hFile = (HANDLE)-1;
 
ULONG ulCount, ulFilSiz;
  OPENFILENAME Ofn;
 
char szFile[MAX_PATH+1] = "\0";
 
char *lpBuf = NULL;
  BOOL bRet = FALSE;

  if (!GetOpenFileName(&Ofn))                 errKind=ekFDia;
 
else if ((hFile=CreateFile(…))==(HANDLE)-1) errKind=ekOpen;
 
else if ((ulFilSiz=GetFileSize(...))==0xFF) errKind=ekFSiz;
  else if (ulFilSiz == 0)                     errKind=ekFZro;
 
else if ((lpBuf=GlobalAlloc(...))==NULL)    errKind=ekGAlo;
 
else if (!ReadFile(hFile, ..., &ulCount))   errKind=ekRdFi;
 
else if (ulCount==0)                        errKind=ekRdZr;
 
else if (!ProcessTheFile(lpBuf))            errKind=ekProc;

  if (lpBuf) GlobalFree(lpBuf);
 
if (hFile!=(HANDLE)-1) CloseHandle(hFile);
  if (errKind!=ekOK) MessageBox(NULL, errMsgs[errKind]);
    
else bRet = TRUE;

  return bRet;
}

Listing #1 - Alternate Solution

I replied to AL with these comments:

“Hi Al,

This is a very nice solution. It is one “level” above mine in that after analyzing the problem in total, it made imminent sense to rewrite it this way. I only see two potential problem areas. One, keeping the enums and error messages in sync; and two, what happens when more convoluted logic is needed at each stage.

Consider this example case. Assume that we want to process files of zero size and do further extensive processing. That means that we need to bypass the allocation, ReadFile and subsequent (possibly redundant, as Brian Grimshaw mentioned) test, but execute the routine ProcessTheFile and the further extensive processing. My type of solution would have had an extra goto that branches to the if containing the invocation of ProcessTheFile. Your code would become something like that shown in Listing #2. Note the need to duplicate the “further processing” code.

Alternatively, you could create a new function containing all the code in “extensive further processing” and invoke it in the two place where it is needed. While this solution could lead to a cleaner result, often creating a new function out of fragments of existing code is difficult because the old code assumes the availability of local data. This local data needs to be passed to any new function, often by reference or pointer.

Still, your solution still stands up very well. Nicely done and thanks!”

if (!GetOpenFileName(&Ofn))                 errKind=ekFDia;
else if ((hFile=CreateFile(…))==(HANDLE)-1) errKind=ekOpen;
else if ((ulFileSize=GetFileSize(…))==0xFF) errKind=ekFSiz;
else if (ulFileSize == 0)
{ // braces needed
  if (!ProcessTheFile(lpBuf))               errKind=ekProc;
  else
  {
    // further extensive processing - duplicate of what
    // appears below
  }
}
else if ((lpBuf=GlobalAlloc(...))==NULL)    errKind=ekGAlo;
else if (!ReadFile(hFile,…,&ulBytesRead))   errKind=ekRdFi;
else if (ulBytesRead ==0)                   errKind=ekRdZr;
else if (!ProcessTheFile(lpBuf))            errKind=ekProc;
else
{
  // further extensive processing
}

Listing #2 - Response to Alternate Solution

And finally, Al’s reply:

“Thanks for taking the time for dialog;

The sync issue: Yes; this is error prone. You may guess I'm an old Pascal devotee'. The weaker C syntax checking lets you declare:

typedef char *tmsgs[4];
static tmsgs msgs={ "zero", "one" };

so msgs[2] and msgs[3] remain uninitialized. The mind reels.

C/C++ must make up in discipline what the Pascal compiler helps you do. Nevertheless, a hack programmer can find ways around even Pascal. Sigh.

More convoluted logic: Yes. The tough decision; do you write very loosely structured code that can be easily modified, to even much different design. the best structure to represent the current design, but risk painting yourself into a corner? - the moderate approach; probably best.

Whether C, Pascal, or whatever, I was much impressed by Niklaus Wirth’s “Data Structures + Algorithms = Programs” I like to (as in this case) tailor the data (i.e. the error msgs) to simplify the logic. Generally I find the code is smaller and faster.

Of course, the next step to many is OOP, and it truly does advance the cause started by “structured programming”. Oh, well, if I go much further, I'll be getting dangerously close to religion. I liked your “contract” approach. I came to about the same thing.

Write function header comments.

Carefully name the function;
- a void function is a verb describing what it does. (includes boolean returns denoting success/failure)
- a function returning a value is a noun describing the value returned. Mostly here I don't return values in pointer parms of value returning functions. I declare these pointer parms CONST.”

3. Design the return value, parms for all data flows. i.e.: Write the function header (prototype) Avoid globals whenever possible.

4. Now (LAST) code the function body. If you MUST "break" the contract, update the header comments IMMEDIATELY.

I rewrote this example to correct for these errors. Can you see any other errors in this modified example.

If I were writing this code from scratch, using OO techniques would simplify the code and reduce the chances of errors.

Editorial

By Reg. Charney

Election 2000

We are very lucky here in the Valley. We probably have more money, opportunities and leading edge product development here than anywhere else in the world. Amidst these riches and abundance, we tend to forget how they came about and the debts we owe to those that came before. We also tend to assume that today’s successes were the direct result of our brilliance and hard work. A lot of that is true—but not all of it.

Much of what we are doing here is the result of R&D and social programs started 20 or 30 years ago. The Internet, lasers/masers, and fiber optics, relational databases, Equal Employment Opportunity are some, just to name a few. More important, these projects were all originally government funded. Government funding tends to be long term and in the areas of pure research. Company funded R&D tends to be short term (driven by the quarter to quarter results) and more application specific. Without pure research, applied research is not possible and without eventual applications, the monies for long term research is not available. There needs to be a balance. As an editor and observer of the Valley, my concern is that with all the sound and fury during this election year, we are forgetting our foundations. Doing so puts our own and our children's future at risk. Please keep this in mind when you go out to vote this November 7.

C++ Standards Meeting

I just returned from Toronto where the C++ Standards Committee was holding their semi-annual meeting.

We are continuing to clarifying the more obscure corners of the language. Typical was making sure that the terms template function (a function) and function template (a template) are used consistently throughout the standard. We are also considering opening up the standards process to extensions, particularly in the library area.

Cool Classes

By Reg. Charney

Global Wrapper

We have all had occasion to use global variables. For example, in Windows programming, they are used extensively. However, they suffer from a number of major problems.

They pollute the namespace—it is easy to accidentally have two variables with the same name in different translation units communicate with each other at run-time because the linker thought they were the same variable.

Within the same module, local variables can accidentally hide global variables, or global variables can be used when really a local variable by the same name was omitted by accident.

Lastly, a change in one part of the program can effect another part of the program unexpectedly when the programmer does not realize that the variable is shared by other routines.

First, we need to recognize that there is always at least one global: main() or one of its variants in every program. There are also other globals if you use namespaces—their namespace names. The main idea is avoid possible name clashes. I am going to present three ways of dealing with globals.

The old way—define all global variables individually at file scope.

Enclose globals inside a namespace, like the standard library does with namespace std.

Enclose all variables to be used as globals inside a class definition that is instantiated at global scope.

Listing #3 shows these three methods.

Method 1 has no saving graces. It just happens to be the most common method.

Method 2 reduces the namespace pollution problem to adding just one name. It also has the added advantage that it can be extended without recompiling preexisting modules.

Method 3 has scope, compile-time access control and potentially runtime control. It can also be used with the other two methods. Its main disadvantage is that you must remember to instantiate the class at the appropriate place.

Please note that a namespace is not a scope or storage type. It is simply a name qualification scheme.

struct X { int i; };

// Method 1 - global scope
int v1;
X x1;
void f1(void)
{
  v1 = 1;   // ::v1
  x1.i = 1; // ::x1.i
}

// Method 2 - namespace
namespace MyGlobalNS
{
  int v2;
  X x2;
}
namespace mgn = MyGlobalNS;
void f2a(void) // qualified
{
  mgn::v2 = 1;
  mgn::x2.i = 2;
}
using namespace mgn;
void f2b(void)
{
  v2 = 1; // mgn::v2
  x2.i = 2; // mgn::x2.I
}

// Method 3 - global class
struct MyGlobalClass
{
  int v3;
  X x3;
};
MyGlobalClass mgc;
void main(void)
{
  mgc.v3 = 1;
  mgc.x3.i = 3;
}

Listing #3 - Global Methods

Trends

By Reg. Charney

Job Openings Trends

Last month, you may have noticed that there was a slight up-tick in the number of Software Engineering jobs in demand, particularly here in the Valley. This month proves that that was no fluke. There is still a continued demand for both IT skills in general and software engineering in particular. However, it does look like it is leveling off. The demand now for software engineers appears to be increasing at the same rate as for all IT positions.

(Note: Chart #1 is normalized for the number of jobs in January of this year. That is, the number of openings in January 2000 has been taken as 1.00.)

Language Demand Trend

The demand for all the major computer languages have shown an interesting set of trends since the start of this year. Chart #2 shows the demand for today’s most popular computer languages. The demand peaked in the April-May time frame when demand overall for language skills was at its highest. Since then, demand for language skills has decline until last month when things turned around. Demand for the Internet related languages, XML and Perl have increased while the demand for Java and C/C++ has leveled off. My conclusion is that the desire to connect systems together using shared data definitions and universal scripting languages like Perl is driving this demand. Visual Basic is the anomaly. Demand for it increased, probably because of new prototyping being done.