Departments


We Have Mail

Letters to the editor may be sent via email to cujed@rdpub.com, or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.


Editor,

I have been a long-time user of const and concur with Dan Saks that it increases program correctness. However, like most things, applying const does have some drawbacks. If you charge headlong into applying const without being aware of the drawbacks you can quickly become disillusioned.

The first drawback I have noticed (in C) is generic containers where a pointer to some object is maintained. Typically the API will accept a const void * to add a new object to the container; void * because it can accept a pointer to any object, and const because it promises not to change the object pointed to.

But on retrieval from the container, the API must choose between either void * or const void *. Neither is correct; if the user is placing actual const objects into the container, then returning void * is wrong. On the other hand, if the objects are not const, returning const void * is inappropriate because an explicit cast to remove the fake constness will be needed.

I assume templates sort all this out in C++ as it is then trivial to instantiate two classes — one for const T * and one for plain T *. But I have not had enough experience with C++ to know if this is true.

The second drawback with applying const is that you must start at the bottom of the software heirachy, or resort to nasty casts. For example, imagine the following code in a pre-const situation:

/* "name" is not modified */
void do_one(char *name);
void do_two(char *name1, char *name2)
 { /* neither is modified */
 do_one(name1);
 do_one(name2);
 }

Starting out on a crusade to add const to do_two will soon result in a diversion to add const to do_one (or the addition of a cast to remove the const before passing it to do_one — yike!) If the software layers are deep, and/or the routines many, a small task can explode quickly. This problem is made worse if do_one is owned by another team, or perhaps another company.

However, even given these two problems (and there are no doubt others) I would still strongly agree with Dan that using const as much as possible is a good thing.

David Brown.
dcb@geko.net.au

Committee X3J11 faced just such issues when we added const to the C Standard in the early 1980s. Most of the compromises worked out fine, but not all. Still, I agree that const was a significant positive addition to C. — pjp


Dear Mr. Perez:

I noticed your letter to the editor in the February 1997 edition of C/C++ Users Journal, and thought our recent experience in cross-platform development might be of some interest to you.

My company, Office Tech, Inc., is a provider of EDI (Electronic Data Interchange) products to the medical market. We install software in our clients' offices, integrated with their OS and their application software. We currently support DOS, Windows, SCO Unix, SCO Xenix, and AIX RISC (IBM RS6000). We will soon implement HP/UX, and the AS/400 is on the drawing boards.

From your letter, it would seem that what we have done roughly approximates what you are considering tackling.

Mr. Plauger answered your specific questions, but I'd like to emphasize and expand on the points he made:

(1) There's GUI and there's non-GUI, and never the twain shall meet, as far as I can see. You can certainly write non-GUI stuff and run it in a DOS window under 3.11, W95, or NT, and if that will do, no sweat. For some requirements, that's perfectly OK. We do it, so you can.

But if what you're really looking for is something that users will feel comfortable with in a Windows environment ... standard point-and-click stuff ... but you want the same program to also run in a non-GUI world ... well, lots of luck. The problems are utterly different, not just in coding, but in basic design as well.

(2) If you have to run in standard DOS, forget about SQL. As far as that goes, forget about most off-the-shelf DBMS packages. They just won't fit.

We spent a lot of time and money with false starts, until we learned a few basic lessons the hard way. Here are the four I feel are the most important:

(a) You must define very precisely your lowest common denominator. Worry about DOS first. If your software must run on standard (non-DPMI) DOS, in 640 KB (actually, more like 400 KB after TSRs, etc.), you have an entirely different problem than if you can count on a memory-rich DPMI environment. Some people run odd-ball DOS-like OSs which won't tolerate DPMI, and there are tons of old PCs out there for which DPMI is Buck Rogers stuff.

If you must run in standard DOS, which we must, your problem will be considerably tougher. Most C++ packages available off the shelf, particularly DBMS packages, are memory hogs, and with only 400 KB to play with, that's a big league problem. We solved it, eventually, but it took us a lot of time, and forced us to reinvent a bunch of wheels.

(b) Beware of buying "magic answer" packages, particularly DBMS and non-GUI user interface packages. I never ran into any outright dishonesty, but there were nearly always several important problems that made the packages very tough to use. We spent a lot of money trying packages that didn't quite fit our needs, particulary the man time required to find out they wouldn't do.

(c) Buy the book POSIX Programmer's Guide, by Donald Lewine, ISBN 0-937175-73-0, and make it your Bible. DOS is not POSIX compliant, of course, but many Unix and Unix-like environnments are. If you are POSIX compliant, you'll still have problems, but they will be much more manageable.

(d) Get used to the idea that developing good cross-platform software is a very tough undertaking. Cross-platform C++ systems require far more time to develop and debug than single platform systems. Whatever you do, don't commit to deadlines until you've had a chance to get acclimated to the cross-platform world. If we had any reasonable alternative, we'd stay miles away from cross-platform. We don't have an alternative, so we have to work hard to get good at it. The learning curve, however, is unreal. Make very sure this trip is really, truly necessary, 'cause it's going to cost you an arm and a leg.

I wish I could say that we've mastered all the intricacies of cross-plafform C++. We haven't, and we never will, because we're shooting at a moving target. About all one can hope for is to get a little less incompetent, one faltering step at a time.

Good luck!
Best regards,
Bob Gebhart, Director
Systems Development Office Tech, Inc.


Dear P.J.:

I read the March '97 issue with great interest, almost all of it was relevant to current projects. I would like to point out a solution to the Multiple Inheritance problem that Martin Remy apparently did not consider in his article on "Portable Signal Handling Under UNIX." There is a fairly simple method which can be used to incorporate the SigAware class into an object without using MI, and that is through a form of object adaptation [Design Patterns, p 139-150].

An example is provided below:

class NonMI : public Base
{   class DerivedSigAware : public SigAware
    {   NonMI *m_parent;
    public:
        Derived(NonMI *parent);
        virtual void
        sighandler(int iSig);
    }   m_SigHandler;
public:
    NonMI();
    void writeCache();
}
     
NonMI() : m_SigHandler(this)
{
}
     
NonMI::DerivedSigAware::
  DerivedSigAware(NonMI *parent)
{   m_parent = parent;
}
     
void NonMI::DerivedSigAware::
  sighandler(int iSig);
{   switch (iSig) {
    case SIGTERM:
    case SIGINT:
        m_parent->writeCache();
    }
}

This change required the addition of one data member to the DerivedSigAware class, and a constructor. By my count this is all of four lines of code.

Note that in the example, I made DerivedSigAware part of the definition of the NonMI class. This (to me) shows the links between these two classes. Furthermore, DerivedSigAware could conceivably require friend access to NonMI. In this case, it makes it easier to observe the dependencies between the two classes as I maintain them.

Keith Boone

Thanks — pjp


Dear CUJ:

I enjoyed Harry Cheng's article on CGI programming ("CGI Programming in C," November 1996). However, I had a problem with his code.

First, I presume that he omitted error checking in the interest of brevity. For example, the code assumes that malloc never fails. More importantly, the unescape function in Listing 3 is flawed. The line reading:

if(url[x] == '+')

should refer to url[y], not url[x]. A similar problem occurs two lines later, where the original reads:

else if(url[x] == '%') {

which should likewise refer to url[y].

The problem shows up most clearly when the string to be unescaped contains more than one escaped character. The code as published mangles the string; with the corrections noted above it appears to work correctly.

For the sake of his users, I hope this mistake reflects a typo in CUJ rather than a bug in Mr. Cheng's code.

Scott McKellar
jm407a@multi.sbc.com

Author Harry Cheng replies: Thank you for your comments about my article. The CGI code presented in the article was mainly tested using my C interpreter under different Web servers. Like in Perl, these CGI programs do not need to be compiled to run. My C interpreter has features such as nested functions. The unescape function is handled as a nested function inside getnamevalue function. I created separate programs without nested functions for ANSI C users. The problem in the unescape function pointed out in your comments had been fixed more than one year ago. But, unfortunately, the fix has not been updated in these ANSI C modules. The CGI code in the article reflects the implementation of the intrinsic function malloc in my C interpreter, which checks on the availability of memory. An updated version of CGI C code with checking is available on the WWW at the URL address http://iel.ucdavis.edu/code/cgi/.


Mr. Plauger:

When I was first exposed to C in the late 1980s, main was typically declared as

void main(void)

and the main routine was exited by using the exit function. Now the declaration is typically

int main(void)

and the main routine is exited by using return. What was the cause of this change, and when did it happen?

Regards,
Robert H. Penoyer
rpenoyer@pacbell.net

No change, just improved standardization. Since the earliest days of C, the function main has taken (at least) two arguments, describing the parsed command-line string that invoked the C program, and returned an int value, describing the exit status to report back to the caller. Many compilers tolerate various mis-declarations of functions, so people often declare main as having fewer arguments than is actually the case, or of returning void rather than int. But the C Standard permits only two variants: int main(void) and int main(int, char **). — pjp


Dear Dr. Plauger,

I asked one of CUJ's sales reps a question that he didn't know the answer to, but he thought you might: are there very many programmers still surfing the Web at 640x480, or are they all at 800x600 or larger, these days?

I'm pretty new at this Web stuff, and trying to decide whether the screen shots on our new Web page should be full-sized 640x480 (which look nice, but which are a bit too large to view in a browser's view window on a 640x480 screen), or scaled-down (which fits nicely even at low-res, but which causes the screen shots to look ugly, sometimes almost unreadable, because some pixel-sized lines and dots disappear).

Do you have an opinion on this earth-shaking question?

Regards,
Dave Burton
(A former Whitesmiths C user, believe it or not.)
dburton@burtonsys.com

I can tell you the shop standards for HTML we use at Dinkumware, Ltd. For Web sites, we review for appearance all pages with Microsoft Internet Explorer 3.0 and Netscape 3.0 at 800x600, 256 colors. But we avoid any graphics that don't fit well on a 640x480 display. We figure that people with older browsers and smaller screens face worse aesthetic problems than we present. For our on-line reference manuals, we stick with a subset of HTML 1.0 (honest). Computer text lines should not be longer than about 60 characters, and graphics must be no wider than the longest computer text line. — pjp


Editor,

Very informative article on "Templates and Today's Compilers," in the January 1997 issue. I wish it had seen print early in 1996. I suffered through quite a few of the same problems that year.

The situation is even more twisted than the authors have documented, however. A couple of examples: Some compilers may have trouble when nested classes/structs are used with templates. Some compilers may report misleading (or as the authors pointed out, ambiguous) errors.

An example is the HP C++ compiler. It sometimes reports an error that seems to be related to constness, when it is really a problem with its instantiation mechanism.

Some versions of HP C++ (such as 3.75) use cfront. Those versions create a sort of database, defmap, to keep track of where to find information for instantiation. Unfortunately, HP C++ (and probably cfront itself) has some problems maintaining defmap. There is a manual workaround. One must define nmap<nnn> files (in which you provide information that defmap has failed to intuit).

By the way, I have a new version of Hangman on my whiteboard. You hang a programmer from each of the crosses (that we bear) in C++.

Scott Maley
sdmaley@smtpgate.read.tasc.com

Template problems are endless, but that article provided a useful summary of the commonest ones you face when trying to write portable code, I felt. — pjp


Dear Dr. Plauger,

I have a big (for me...) and urgent problem. We are using Borland C++ 5.0 to develop 16-bit and 32-bit Windows software. We want to change the functionality of our program at runtime by using DLLs.

For example, we have a class A in the executable. Derived from A, we have a class B. Class B is implemented in a DLL. By changing the DLL at runtime (to one that has a different implementation of class B) we are able to change the behavior of class A.

The first possible problem could be caused by the DLL using functions implemented in the executable. The compiler and the linker have no problem with this after putting the necessary __import and __export declaration modifiers at the right places. It appears that the DLL is using the EXE like it is a kind of a DLL. Is this possible?

The real problem we have is with using templates and DLLs. The DLL is using templates which are defined in the executable. After linking the linker is generating the following kind of messages:
"Warning: Extern PSubject::Attach(PObserver*)' was not qualified with __import in module
Error: Unresolved external TVectorImpBase<TVoidPointer,
TStandardAllocator>:: Resize(unsigned int,
unsigned int)' referenced from module source\hypotrus\daflvrij.cpp"

This is correct because these methods are part of a class that is implemented with the use of a template and the template is missing the right declaration specifiers. When I try to add these declaration specifiers, the linker reacts with other errors (about wrong or unknown syntaxes).

My questions are:

How do you use templates regarding to DLLs (define the template in the EXE or DLL, and use it in the DLL or EXE)?

Where do I have to place the __import and __export specifiers when using templates?

When I browsed through the Borland OWL code I noticed the following code. It looks as this could be some key to my problem:

typedef TDllLoader<TRichEditModule>
    TRichEditDll;
#if defined(BI_IMPEXP_TEMPLATES)
# if defined(_OWLDLL) ||
     defined(BI_APP_DLL)
  //
  // Export template of
// TDllLoader<TRichEditModule> // when building ObjectWindows DLL // and provide import declaration of // DLL instance for users of the class. // template class _OWLCLASS TDllLoader<TRichEditModule>; # endif #endif

_OWLCLASS expands to the right declaration specifier: __import or __export. Using this in my own code yielded no result though.

I hope I gave enough information to make my problem clear, if not I will happily answer your questions. Any hint or solution is very welcome.

Yours,
Marcel Beekman
PCL Software B.V.
MBeekman@pcl-software.nl

Sorry, I can't help you here. Perhaps one or more readers knowledgable about Borland v5.0 can help. I do know that templates and DLLs are a heady combination, for which the draft C++ Standard offers little or no guidance. — pjp