Dear Editor,Bjarne Stroustrup says of reference arguments (The C++ Programming Language, 2nd Ed. p. 62), "However, to keep a program readable it is in most cases best to avoid functions that modify their arguments." Dan Saks, however, says (C/C++ Users Journal September 1994 p.89)
int genq::remove(void *&e) { ... e = p->element; ... }Richard Clarke says I don't much care who's right but agreement needs to be made one way or the other.Yours faithfully,
Richard A. Clarke
R.Clarke@ee.surrey.ac.ukThere are indeed two schools of thought on functions with side effects. A pure functional programming style views side effects within a function call (or indeed within any expression) as dangerous. The argument is that the code has surprising effects not directly discernible from reading the expression that calls the function. C/C++ programmers, in general, are more pragmatic. They cheerfully write autoincrement operators, embedded assignments, and function calls with side effects whatever gets the job done with an economy of notation and executable code. Stroustrup seems to be arguing for a tempered version of pure functional programming. Saks shows the valid use for reference parameters (as opposed to const reference parameters). Programming practice is too diverse for me to see a serious contradiction here. pjp
P. J. Plauger, Editor, C/C++ Users Journal:
Have you ever needed a rewind function for C++ istreams? The usual alternative is the less elegant pair of calls:
istrm.seekg(0); istrm.clear();Instead, I use a rewind manipualor, which I define in the file rewind.h (see Listing 1) .The rewind manipulator does for C++ istreams what C's rewind function does for C file streams. Note that rewind can be used with any descendant of istream but is most appropriate with ifstreams.It's used like this:
#include "rewind.h" ... ifstream InputStream("filename"); InputStream >> a >> b >> c; // read three items from InputStream InputStream >> rewind >> a2 >> b2 >> c2; // rewind and read three more items // note: a==a2 && b==b2 && c==c2In the above example, a and a2 are extracted from the same part of InputStream (as are b and b2, and c and c2).Please share this code with your readers if you think they'll find it useful. (The code also serves to demonstrate one way to define C++ manipulators and how easy it can be.)
Paul Hepworth
s165r@cc.usu.eduYes, manipulators have endless possibilities. To comply with the draft C++ Standard, you have to modify this in several small ways, but the idea is basically a good one. pjp
Editor,
Thank you for a wonderful magazine! I have been a reader for many years and a subscriber for several. I get several C-oriented publications off the shelf on an irregular basis, but I have found that only your magazine has enough information to the general programmer to subscribe to.
Thank you also for the fax-back service offered from the magazine. Many of these are available for various publications, but they are all long-distance. I hope that you can continue offering this service to your readers in the future. (I understand that someone must foot the bill, and if it's not me, it's probably your publication.) If you could pass on my thanks to those businesses who support this service I would appreciate it! At my day job, my office can afford the long distance calls, but for my after-hours programming and personal work, I cannot. Some of these advertisers may recognize my name or my company's name as information obtained through this service has led to several purchases for myself, and for my office.
This service is wonderful for the struggling small business that watches every penny. I have recently spent a fair amount of money to get my home business started on the right foot and I would not have been able to get as much as I did, if it weren't for this service!
Once again, I thank you for providing this service, and I thank your advertisers for participating!
Jeffry Brickley
Lockheed Engineering and Sciences (by day)
TimeWarp Software (by evening)Dear P.J.Plauger,
I am writing a C++ program for Windows using Borland C++ 4.0. The program makes use of a large tree of small objects, which is built during initialization. After initialization the tree is read-only during the rest of its lifetime. The program worked fine until the size of the tree approached 64 KB; seemingly, I ran out of memory. Under Windows memory is split into "near heap" (near pointers, total size < 64 KB) and "far heap" (far pointers, total size: all free memory). The problem arose because I used the C++ operator new to allocate objects. new works only on the near heap. It returns a near pointer (void near*), and you cannot overload new to return a far pointer (void far*). I am not sure how general the problem is, but my solution may be of general interest.
To solve the problem objects must be allocated on the global heap, of course, paying the penalty of accessing the objects via far rather than near pointers. I must find ways: (i) of managing the global heap and (ii) of allocating objects there. With regards to (i), a very simple implementation is possible, because all objects are created at initialization time and remain unmodified until the end of execution. (See Listing 2. )
Function New returns a far pointer to a memory block of the requested size. You cannot specifically delete such a block, rather the whole Heap object goes up in flames as its destructor is called. GlobalAllocPtr and GlobalFreePtr are Windows functions for maintenance of the global heap. To find the address at the offset pos bytes within the current block, the starting address is cast to a pointer to character. The pos added will then count as bytes.
To address (ii), I have this suggestion for allocating objects of some class X on the global heap:
extern Heap heap; class X { ... virtual X far* Clone() { X far* p =heap.New(sizeof(X)); *p = *this; return p; } };The Clone function simply makes a copy of the X object on the global heap and then returns a pointer to that copy. The copy constructor of X should be implemented to ensure that the assignment above works out. If deriving a class Y from X, it should have an implementation of Clone() similar to this. Otherwise, X::Clone() would be called for Y objects with disaster soon to follow.The above definitions could be used like this:
// Global heap object Heap heap; main(){ // Three far pointers to X; // initialized in different ways (i-iii): X far* pa, far* pb, far* pc; // (i) From a local object X a("Rip", 2); pa = a.Clone(); // (ii) From an object on near heap X near* b; b = new X("Rap", 3); pb = b->Clone(); delete b; // (iii) From a temporary object pc = X("Rup", 7).Clone(); }Any comments on my design in general or on heap management under Windows in particular are welcome.Yours,
Niels Holst,
Zoological Institute,
University of Copenhagen,
Denmark
E-mail: NHo1st@zi.ku.dkUnless I'm missing something, it seems to me that placement new is a better mechanism for this sort of thing. pjp
Dear Editor:
I have an application for which I would like to generate random English pronounceable proper names. I have looked in the common sources (Knuth, Sedgewick, Wirth, etc.) for a suitable algorithm, but haven't had much luck.
Have you ever come across such an algorithm or one that could be adopted?
Sincerely,
Edward B. Bell
Integrated Solutions, Inc.
6618-18 Reafield Drive
Charlotte, NC 28226No, but I can think of some puckish applications for such an algorithm. I hope one of our readers can help you find one. pjp
Dear CUJ,
There is a bug in Listing 1 on page 78 of your April 1994 volume. The loop control variable i is incremented within the initialization loop with the result that only even indices of the freq_count array are set to zero. Also, errors will occur if characters are signed and strings containing characters with ordinal values 80-FF hex are input, since this will yield negative indices into the freq_count array.
Sincerely,
John S. Hanus
Houston TX, 770P.S. Would you please tell me how I might obtain a recent salary survey for computer professionals?
Thanks for the bug report. You might try one or more of the IEEE publications. Seems to me they publish regular surveys covering our field. pjp
Dear Mr. Plauger,
When I saw an article about an extended date library in your October, 1994 issue, I was hopeful. There is just so much disinformation these days about calendars and dates that I had hoped this article would clear up some of the confusion. Unfortunately, I was somewhat disappointed after reading the article.
In the article, Mr. Milam explains that his date function "returns the number of days elapsed since 1 January, 0001 A.D." Such a statement implies that the date function is valid all the way back to that date. I often find myself dealing with dates in the Middle Ages, so this looked like something I could use.
Being skeptical, however, I decided to check the accuracy of that claim, specifically for dates before October 15, 1582, the date the Gregorian calendar went into effect. (For practical purposes, this is true even though some countries did not accept the Gregorian calendar until several centuries later. Regardless of when the calendar was accepted, there is a discontinuity caused by the removal of ten days so that Easter would fall closer to the spring equinox. In the Catholic Church, this discontinuity occurred between October 4th and October 15th, 1582.)
To his credit, Mr. Milam does mention in a sidebar that "This value presumes the Gregorian calendar in use today was implemented on 1 January, 0001 A.D., (which of course, it was not)..." No mention of this is made in the main article, however, which might cause some people to be misled into thinking that the date function can be used for calculations, such as day of the week, and days between dates, regardless of what side of October 15, 1582 the days fall. In fact, the assertion that the value of date is the number of days since January 1, 0001 A.D. is false even for days after October 15, 1582 because before this date, the Gregorian calendar and the Julian calendar should be identical.
As an example, date will return a value of 728,202 for October 1, 1994, but the correct value is 728,203. The difference is due to errors in two places. The first, as noted before, is due to the discontinuity in the Gregorian calendar and the different method of calculating leap years before that date. In order to compensate for this, the following adjustments must be made for dates occurring after October 4, 1582:
Cumulative adjustment: +2
- Adjustment for leap centuries divisible by 400: (1582 / 400) 3
- Adjustment for leap centuries divisible by 100: +(1582 / 100) +15
- Adjustment for dates removed from calendar: 10
The other error is in not adjusting for January 1, 0001 since, by definition, zero days would have elapsed between this date and itself. I therefore subtracted one to obtain the correct result. In order to obtain the correct result for dates after October 4, 1582, the result should therefore be increased by one. For dates before this, the year calculation should omit the adjustments for leap centuries and then one should be subtracted from the result to account for January 1, 0001.
The modified code would appear as shown in Listing 3.
While sidebar articles are a nice way to delve into related subjects, I sometimes don't read them unless I'm interested in more background information. In my opinion, information which relates to the correct use of a program or procedure library should appear in the main text of the article.
Even with all of my complaints, I am glad to see someone publishing something about dates and calendars. This is practical information we all can use. I look forward to seeing more articles of this kind in the future.
Sincerely,
Michael R. Kabala
Davenport, IA 52807Every time I think I have nothing new to learn about calendars... Thanks. pjp
Greetings:
What is this that I see in the July 1994 issue of CUJ? Why it's a YASM (yet another style manual). Complete with the imperative shall. I guess we are doomed to repeat history endlessly.
Some 15 years ago I began writing code with curly braces in two languages Ratfor and Pascal. The curly braces were used for entirely different reasons in the languages and it created no small amount of confusion amongst my Pascal colleagues when I would show them my Ratfor code. And, naturally, curly braces were not treated at all kindly by the Fortran crowd.
So, I took a defensive tack and put some macros in the wrapper for my Ratfor source code that defined begin as { and a variety of end statements (end_for, end_do, end_if, end_while, etc.) as }. While I was at it, I equated the ==, != &&, // notations to Fortran like symbols eq, ne, and, or respectively, and ! to NOT. Moreover, the if-then-else craze was in so I defined and used an empty then statement at the end of my if statements. My Fortran friends settled down some; my Pascal pals seemed mollified.
When I migrated to C in 1982 I took most of this baggage with me. Using this stuff, I talked several assembler fanatics into trying C. What really turned them on were the ++ and -- operators. One of the benefits of using lettered-operators was that my supervisors, who typically knew little or no C, could read my code. I wrote thousands of lines of C code this way.
Then, in 1986 I moved to an organization where C was the preferred language. The programmers were, by and large, C strict constructionists. To them, what I had written simply wasn't C at all. Well, a hybrid maverick dialect maybe but unacceptable. So I ended up writing a tool in lex to convert my lovely code (KB's stuff, they called it) to regular C. I converted thousands of lines of my library utilities.
The point in all this palaver is that I have come to the conclusion that it is preferable to stick to the language as provided and let the uninitiated readers and bystanders fend for themselves. As for team members, if they are carrying excess baggage from another language, they can hoist it overboard in time. Moreover, if new members to the team are experienced C programmers, their learning time is shortened.
Successful software projects are the result of sound programming principles. Careful, thoughtful, thorough design, coding and checkout will do more for a programming project that all of the synthetic paste-ons that the new idealists love to promulgate.
Sincerely,
K. B. Williams
Stillwater, OKI agree with most of what you say, with an added proviso. Whatever a programming group settles on in the way of a uniform style is always better for the group effort than any individual's notion of good style. , pjp
Dear Mr Plauger,
Apparently I'm one of the remaining few who find C's attractions outweigh its many distractions. Even at this late date, I find myself keeping an eye out for books and articles on C in the hope that yet another of its hidden truths may surface, intentionally or unwittingly revealed at last. I do this because I see in C a language whose real potential lies rooted in its remarkable if somewhat inchoate, capacity to support novel, even advanced programming techniques and dialects.
Unfortunately this aspect of C is rarely touched on publicly. C's popularizers tend to spend their time re-hashing its syntax and precedence rules or they make a career out of reminding us of the tricks and traps any respectable construct is capable of entertaining. Occasionally they even go on and on about the intricacies of the command-line, mentioning its limited relevance for today's world.
Project-sized program development is where it's at today, moving the programmer's concerns so far beyond the command line that it's almost become a fossil. For one thing, we no longer test from the command line with debug options unless we're nearly brain-dead in the first place.
So what then if while testing your latest fantabulous brain-child, your computer goes idling off into infinity, hobnobbing with chaos, dancing the herky-jerky (formerly known as the black death), what else is new? If you know what you're doing within a matter of minutes you've located the trap-door through which that errant blankety-blank fell.
You're able to do that because the limbo of faulty technique is, itself, flawed. It can't even rear its ugly head for a last malicious leer without inadvertently revealing its odiferous self. That's because design problems (apparently with teleological foresight) have always excelled at setting what appear to be bottomless-pit booby-traps, even though they actually aren't.
The rumor persists that they exceed code problems as breeders of man-eating bugs, so we must at least continue pretending to respect them. Just be careful, though, where you seek help when you think what you've got is a code problem. Unless you're working on someone else's grossly over-extended project, are trying to mesh with the other guy's buggy code, or are voyaging into a complex, new (is this trip really necessary) environment, you'll find debuggers largely irrelevant. If you can learn to avoid the swamps of fantasy land, you'll rarely need one.
That doesn't mean we've all suddenly become omniscient. Most programmers (if they're any good at all) do serve a life-long apprenticeship. At least, they do as far as the analysis and design stages of program development are concerned; but if they wallow in the myriad ways to code this or that construct, they're rarely very productive. And yes, there are always beginners thirsting for knowledge of the basics, but the day they monopolize anyone's readership to the exclusion of all else, someone's in more trouble than they realize.
That may be why it's a good idea for any programmers' periodical to include coverage of the big picture; if not regularly at least frequently. I don't think, for example, that I'd need the fingers on more than one hand (not even another digit or two) to count the number of times I've come across a treatment anywhere of the semantics of programming in general, let alone that of C.
Many of us, believe it or not, have only an occasional or quickly passing interest in bit manipulation, pointer arithmetic, multi-level indirection, type conversion tricks, porting, porting, porting, etc, etc, etc. We deal with these matters rarely but carefully; once we've found reliable, results-oriented methods for handling them, that's it. We definitely ain't preoccupied with or troubled by them no more.
And certainly, none of us has to write many programs in any language before we realize we've bigger fish to fry if we're to become proficient. Even beginners soon grasp the significance of learning the system requirements for putting together multi-module projects. (Another subject I've never seen treated adequately anywhere.)
Of course, acquiring the skills needed for tackling larger projects does entail, among other things, learning to handle memory management, which in turn requires one to become agile at sidestepping any guru gobbledegook espied along the way, especially the kind designed to persuade the gullible they're facing another of those ubiquitous, "mystery wrapped in an enigma" boogeymen the Elect erect around every turn in the road.
It also means learning to juggle a motley crew of I/O knives, plates, and bowling balls with considerable skill and dexterity (now there's some cans waiting to be opened).
It doesn't hurt a bit either if, somewhere along the way, one works up a minimally comprehensive, rock-solid library of heap-oriented Abstract Data Types (perhaps some of those very ADTs so summarily dismissed from public scrutiny when OOPing first appeared on the scene). Without them, even in C only a limited level of sophistication is possible.
Of course, an alternative to using your own ADT library is to use C++. But replacing your ADTs with C++'s Ptolemaic version of the OOP-iverse can do strange things to your view of the night sky. True, C++ does have its merits. It also has its share of leaky balloons, sluggish ships of the OOP-osphere, unable to stay aloft without a lot of huffing and puffing by the latest version of the cutting-edge.
Suppose we did elect to use C++ selectively and rarely, preferring instead to develop our own in-house C dialects, would that mean the language might soon find itself teetering on the edge of the Babelization precipice? Not to worry. C's been perched precariously on the next ledge over for years. Who among us hasn't already found it annoying (or worse) to have to read radically different styles of perfectly legitimate C code on a regular (almost daily) basis?
(Let's let he who cast the first stone... My guess is he's probably gonna do it anyway.)
Quickly now, do you know of any other programing language in the whole computerized world that lets you almost completely redo itself for your own nefarious purposes? Where else can you erect a highly articulate, linguistic super-structure of your own design without risking the wrath of The Great Pooh Bah? Isn't this what programming at its most fascinating and fullfilling is all about? After one strips away the vestiges of commercialism, with its transient aura of achievement and reward (even earthly power for those on an egotrip), what's left?
Haven't we always entertained (secretly of course) a yen to use our computers as extensions of our minds? Well, C is one programming language (par exellence at that) which allows us to do just that. It beckons us to explore and invent. It invites us to create new, viable alternatives (ultimately perhaps, even one to supplant itself).
When you get right down to it, I'm sure this has a lot to do with why I still count myself among the remaining (lucky) few.
Truly,
Mark Rhyner
Uh, yeah. Thanks for sharing those thoughts. pjp
Gentlemen:
I have discovered a bug in the code for your program dump.c on page 70 of the September 1994 issue of The C/C++ Users Journal. The following line of code:
fprintf(stderr, "Can't open %s\n"); should read as follows: fprintf(stderr, "Can't open %s\n", argv[1]);Otherwise the file name is displayed as machine garbage.I have been a faithful reader/subscriber to The C/C++ Users Journal for over five years now and a C user for over seven years, although at this point I still just dabble in the "basement." I have found your magazine to be quite useful and have saved every issue since I became a subscriber. I felt I should bring the aforementioned program bug to your attention.
Sincerely,
David P. Williams
Thanks. pjp
Dear Mr. Plauger:
With interest I read the letters of Mr. Singleton and Mr. Larsson concerning the memory allocation within Windows 3.[01] in January issue of C/C++ Users Journal.
Isn't it interesting how something like malloc can be screwed up in this wonderful OS? I usually program in the dated and complicated OS UNIX where I simply call malloc and don't have to worry about segmented architectures. I wonder why people think that Windows is such a great improvement over other operating systems. Could it be that they really mean is it is an improvement over DOS?
Agreed, it is nice to have all of these colorful icons and windows. Agreed, it is easy to use as long as it does not hang up, destroy five hours of work or anything like that. However it is a pain in the neck when it comes to programming it.
I once had to port a UNIX library to Windows and believe me, I think it is a very good reason to get out of the programming business altogether and start selling newspapers or do anything that makes sense.
Sincerly,
Detlef Engelbrecht
Hamburg, Germany
detlef@isys.netA segmented architecture certainly adds complexities, particularly for a language like C that evolved on machines with flat address spaces. We can hope that the emerging 32-bit flavors will eventually end the need for several flavors of pointers, and storage allocators to go with them. pjp
Editor:
I hereby add my voice to those who enjoy CUJ, and wish we had found it sooner. (And, being slow to convert, would like the scales tipped in the direction of C)
The "Conversions and Casts" article by Chuck Allison in the September 1994 issue of CUJ was excellent. Articles of this type provide review and instruction to us all.
I would question one section of the article (and I may be misreading what he is saying). In the Arithmetic Conversions section, Mr. Allison states that "The roundoff error inherent in finite-precision arithmetic caused the product 100 * 23.4 to land closer to 2339 than 2340." C does not round when it converts from a float to an int, it truncates.
Because, as Mr. Allison points out, floating point numbers are only approximations and not exact (the degree of approximation being machine-dependent), the result of a floating-point multiplication of 100 (which is implicitly promoted to a float) and 23.4 is represented in the machine as a fraction less than 2340.0. Therefore, when the result is assigned to an int, the precision is truncated, and only the integer portion is assigned. The reason the conditional statement inside the printf also fails to recognize the result to be 2340.0 is again because of the result being an approximation which is compared against a constant exact value of 2340.0.
My apologies if I have misread the text of the article, but I thought it needed to be made clear.
George B. Durham
Your explanation is accurate enough. I believe, however, that Chuck Allison was using "roundoff" in the most general sense. "Rounding to lower" is a fancy term for truncating, and "rounding to nearest" is another term for the colloquial rounding. pjp
Dr. P.J. Plauger:
I just read your January review of Anthony Porter's book, The Best C/C++ Tips Ever and I can't tell you how impressed I was at the way you shredded this Porter character. I'll bet he was really surprised.
I would have been. For months your magazine has carried full-page ads for a software giant who sells a buggy linker, a buggy debugger, and now it's revealed (thanks to some of your competitors) even a buggy compiler. Meanwhile, the vendor's customers are advised to call the company's $2.00 per minute advisor line if they have complaints. What really surprises me is that your apparently overwhelmingly strong sense of ethics hasn't compelled you to mention something about maybe as many as one of these problems.
Mr. Porter's book was published by McGraw-Hill, while I notice that my copy of your book The Standard C Library was published by Prentice-Hall. That probably has no relevance to anything, but I'm trying to understand why you choose to kill flies with a hatchet while you spray perfume on nuclear hooligans.
Perhaps you'd like someone to believe that because of your hatchet work you really must be some sort high principled, rock-ribbed he-man editor. To me, it indicates both a lack of principles and testicles.
Sincerely,
Bob Moore
cc: McGraw-Hill PublicationsYour thorny prose demands a few well chosen responses. First, please note that I did not shred "this Porter character." I shredded his book, which stands or falls on its own merits. Ad hominem attacks say more about the attacker than the target, as a rule. So too does a person's ability to distinguish between criticism of things and people.
Second, I have personally written a number of books and several commercial compilers, so I'm keenly aware how hard it is to do both. I am still actively writing books and software, so I have scant time for much anything else. If I review a book instead of a major software package, that speaks more to the relative effort I can spare than to the relative importance a third party might ascribe to the two efforts.
Finally, I have to confess that I care not one whit what you think about me or my motives. Anthony Porter tried to write a good book and failed, at least in my estimation. I respect that effort far more than I respect bystanders who sling mud with neither restraint, accuracy, nor a sense of direction. pjp
Dear Mr. Plauger:
Since there has been so much interest in date routines lately, I could not resist putting in my two cents worth.
The routine presented by Mr. Magalhaes in your January 1994 issue is, in fact, a rendition of the formula presented by the great mathematician C.F. Gauss (1, 2) which has the acknowledged limited domain of years. A more robust method for calculating the date of (western) Easter which is valid for any date in the Gregorian calendar, that is from the year 1583 on, is given in Listing 4.
I came across this method in a book by Meeus (3) in which he states that this method was originally given by Spencer Jones in his book General Astronomy (1922). It was then published again in The Journal of the British Astronomical Association, vol. 88, page 91 (December 1977) where it is said that this method was devised in 1876 and appeared in Butcher's Ecclesiastical Calendar!
The routine in Listing 4 can be made more efficient by using the divide with remainder function instead of the repeated divisions. I merely present it in this format as it may be easier for some to follow. For Those who are interested, Listing 5 shows a method for calculating Julian Easter. It is interesting to note that the date of Julian Easter has a periodicity of 532 years.
Well, my two cents are now up. Thanks for letting me babble and keep up the good work. I enjoy the magazine very much.
References: (3) and (4) are general references for Gauss (and others)
[1] Gauss, C. F., Theory of the Motion of Heavenly Bodies (Dover Reprint, 1963)
[2] Reicharot, Hans, ed., C. F. Gauss, Leben und Werk (Gauss Gedenkband, Berlin: Haude & Spener, 1960)
[3] Meeus, John, Astronomical Formulae for Calculators (Willmann-Bell, Inc., 1988)
[4] Boyer, Carl B., A History of Mathematics (John Wiley & Sons, 1968)
[5] Bell, E. T., Men of Mathematics (Simon and Schuster, 1937)
Aris Karimalis
The two functions certainly have an elegant simplicity. Thanks. pjp
Editor,
(Pardon the informality, but) would you mind solving one simple office puzzle for us in a future CUJ article/box? We regularly exit Windows to log users out or in (allowed only in DOS, not a shell) to our Novell-based LAN at work. Isn't there a procedure we could compile and place in batch to simplify this clunky process?
Thanks,
Skip Pletcher
MenSanaI don't know squat about programming for Netware, but I know what you want must be possible. My Netware Lite manager will execute a DOS batch script from within Windows that can, among other things, log me onto the network. Anybody? pjp