Message-ID: <3D19FAEA.DA82CD52@eton.powernet.co.uk>
Date: Wed, 26 Jun 2002 18:33:30 +0100
From: Richard Heathfield <binary@eton.powernet.co.uk>
Organization: Eton Computer Systems Ltd
X-Mailer: Mozilla 4.6 [en-gb]C-CCK-MCD NetscapeOnline.co.uk
(Win98; I)
X-Accept-Language: en-GB,en
MIME-Version: 1.0
Newsgroups: comp.programming
Subject: Re: The Data Quality Act
References: <f5dda427.0206031634.d378860@posting.google.com>
<3D1301C4.CFC87CDE@eton.powernet.co.uk> <f5dda427.0206211823.636ff86@posting.google.com>
<3D14865C.54B206A1@eton.powernet.co.uk> <f5dda427.0206221613.725aae18@posting.google.com>
<3D1579EB.B9B92133@eton.powernet.co.uk> <f5dda427.0206231131.36233992@posting.google.com>
<3D165D5D.C17DCDAF@eton.powernet.co.uk> <a78137c0.0206240445.5ce183ed@posting.google.com>
<3D184267.37D5051D@eton.powernet.co.uk> <3D18AC3E.A9FA9A80@mmm.com>
<f5dda427.0206251750.7d4f9c1@posting.google.com>
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 8bit
NNTP-Posting-Host: 195.60.5.107
X-Trace: news.power.net.uk 1025113806 195.60.5.107 (26 Jun 2002
18:50:06 +0100)
Lines: 658
Path: news.power.net.uk
Xref: news.power.net.uk comp.programming:80375
"Edward G. Nilges" wrote:
>
<a hell of a lot>
Congratulations, Mr Nilges. Your article is actually too large for
my
(third-party) news client to quote in its entirety.
I suppose I ought to write a news client that can handle your article,
but I suspect that will take a little longer than I plan to spend
on
you, so I'm just going to have to cut and paste. <sigh>
I have also felt it necessary to re-flow the text of your article
to a
considerable extent, but this is only a whitespace issue, and I
have not
fraudulently taken the opportunity to edit selectively. In contrast
to
my usual habit, however, I have not indicated snippage; this is
because,
rather than snipping, I have actively selected, copied, and pasted
the
text to which I wish to reply.
> Mr Richard Heathfield [...] has made some striking
> claims about the procedural language C in its 1999
> standardization, as contrasted with OO development
> even in a relatively procedural language that
> nonetheless provides a basic set of OO tools, Visual
> Basic 6. Heathfield's claims appear to include the
> contention that an "object" is a named region of storage.
Actually, I didn't claim anything very much. *You* claimed that
malloc
couldn't be used safely, or something of that nature. All I've
done is
rebut your claims.
The contention that an object is a named region of storage is straight
out of K&R 2. The formal C99 definition of an object is as
follows:
"region of data storage in the execution environment, the contents
of
which can represent values"
(ISO/IEC 9899:1999 3.14(1))
> In response to these claims, I have developed two
> implementations of a string parser in C and in
> object-oriented Visual Basic which the reader is free to
> evaluate use and change.
In the interests of whatever brevity I can salvage, I will endeavour
to
restrict my comments to your points about C. Consequently, I have
completely ignored all your pseud nonsense. That sort of stuff
might
impress you, but it doesn't really impress me; I'm simply not
interested.
> Heathfield's claim is that OO is not worth the trouble
> because of the power of modern C.
I don't remember claiming that. Can you show me a direct quote to
support your statement?
What I /have/ claimed is that procedural programming in general
and C
programming in particular remains viable. That does not mean that
OOP is
not viable.
> We need to see whether this claim can be informally justified,
Whoa there. First we need to see whether that claim was ever actually
made.
> where an informal justification is more rugged
> and less falsifiable than a benchmark.
> By actually coding the C solution and the VB solution
> and critiquing the results, I shall show, in detail,
> that Mr Heathfield is wrong.
Firstly, you're trying to disprove a claim I don't recall ever having
made. Secondly, your C "solution" sells C very short indeed. This
does
not surprise me, I'm afraid.
> However, I'd not used C at all since 1995. As a result
> I made some initial blunders
Fair enough, but don't blame C for your mistakes.
> including but not limited to
> the following.
>
> * I arranged the C functions
in a natural order and
> had to rearrange
them because C requires functions
> to be defined
prior to use in one compile unit.
That's not true. It requires only that they be *declared* prior
to use.
Function prototypes would have done the job just as well.
> * My codes parsed items by
allocating small malloc
> blocks of memory
and returning the pointer to this
> block.
The initial code did not explicitly free
> these blocks.
You appear to have fixed this.
> * I made a few errors in calculating
needed block
> lengths.
You appear to have fixed these, too.
> * I was caught by a non-orthogonal
distinction between
> the return of
strstr versus that of strspn.
Understandable: strstr is intended to find strings, and strspn is
intended to discover the span of a substring. These are orthogonal
IMHO,
but I can see why you might have been caught out.
> * I reversed the use of strspn and of strcspn in the initial version.
<shrug> That's just a look-up thing.
> * I failed to offset a few values properly.
Fixed, I presume.
> I predict that many comments will be made about
> the above list of blunders by Mr Heathfield et al.,
> despite the fact that the greatest programmers,
> especially those whose social position is secured by
> academic tenure, use public, "open source" blunder
> lists to improve their product. For example, Donald
> Knuth of Stanford lists his errors in the development of Tex.
Well, no, I wasn't planning on berating you for problems you have
already found yourself. The only one I'd pick up on is the prototypes
thing, which you really ought to have known, and which might have
made
your life a little more pleasant.
> Heathfield et al. will, I imagine, want to ascribe all blunders
to a
> competence level.
Not the ones you found and fixed yourself, no. But those that /remain/
will obviously colour my response.
Well, at this point in your article you launched into a lot of claptrap,
so I had to scan down to get to something actually worth talking
about.
Here's what I found.
> 5. It is easy to plan the C version.
In this case I
> merely write
the comments describing the function.
> Of course, this
ease results in a lot of bad code.
That's an interesting statement. You say that it's easy to plan
a C
program and that, because it's easy to plan it, you write bad C?
Weird.
> 1. The VB solution, unlike the C
solution, is not a
> toy. That
is, the C solution is both too
> inefficient (because
it will repeatedly scan
> the same string
unnecessarily as we describe
> below) and insufficiently
encapsulated.
Hmmm. This seems to me to be a criticism of your (in?)ability to
write
encapsulated and efficient C.
> 2. Visual Basic provides better
support for strings,
> up to 2 billion
characters long,
C places *no* upper limit on the length of strings. It leaves the
upper
limit entirely to each implementation.
> which may contain NUL characters.
A terminology issue. In C, strings are terminated by null characters.
If
you want sequences of characters with null characters mixed in
with
them, you can have them.
> 4. The code is efficient when factored
by the lack of a need, in
> typical VB applications,
for speed.
This appears to mean "the VB code is slow, but not quite as slow
as
everything else, so that's all right then".
> 5. The development environment is
superior to the Luria C
> compiler. Although
the Luria C compiler provides a
> graphical user
interface, it is not nearly as complete
> and does not
provide Windows standard ways of doing
> things…notably
undo.
Fine, so use a different IDE. This is a non-issue. Note that VB
also
fails to provide standard ways of doing things, such as supporting
vi
keystrokes. This is exactly as (ir)relevant as your point.
> 6. The reusability of the work product
is high because the
> work product
(a Windows EXE) can be changed into a dynamic
> link library
by (1) removing the test form and (2) changing
> the project type.
The resulting DLL can be used readily by
> Windows desktop
programs and with some pain on the Web using
> MTS. There are
furthermore no COM or DCOM dependencies in
> this code, and
it can be ported to VB.Net using the VB.Net
> Conversion Wizard.
The C source, assuming it's been written properly, is of course
re-usable not just on Windows but on any hosted implementation
on any
operating system.
> Disadvantages of the C Solution
>
> 1. The C solution is a toy.
At best it is an example of code
> as would be printed
in an introductory text book.
Not a good introductory text book, however.
> As is it cannot
do high or even medium intensity parsing
> for reasons detailed
below.
The reasons appear to be that you either couldn't or wouldn't provide
that functionality.
>
> 2. Unless one is a "C insider",
reading the code is
> unpleasant
I must be a "C outsider", then. I found it hard to read, and I don't
like code that is hard to read.
> and presents understandability
hurdles including
> the differences
between string library semantics
> and the fact
that pointers must model strings. In
> particular, the
constant freeing of pointers (which
> may be null and
which are therefore freed in a function
> which tests for
null) is an annoyance.
Yes, your code does indeed present "understandability hurdles",
and you
do indeed have constantly to free memory, mainly because you allocate
with carefree abandon where it isn't really necessary. Note that
free()
is required by the Standard to accept NULL and do no processing
on it.
> It may be objected that "Ed, you obviously aren't au fait and
> hip to my C tools which completely replace your primitive use
> of string pointers."
>
> The problem is that we're discussing C…not the language that
> consists of the union of C, and some tools the interlocutor
> has made, bought, or stolen.
I didn't plan to make that objection, although it is of course true
that
most C programmers have built up a considerable library of useful
code.
> 3. The solution has level 1 reusability
(where "level 0"
> would be completely
unreusable code) because to reuse,
> the C insider
has to manually extract the reusable
> functions and
discard string2Display and main. The
> programmer is
then "free" to modify and to break the
> "reusable" code
and, in some environments, to blame
> the initial implementer
of the code for the resultant
> problems.
Actually, a "C insider" would plan for re-use, and would put
general-purpose functions into separate translation units. The
issue of
subsequent modification is a version control issue, not a C programming
language issue.
> 4. The code can readily be changed
to read, through
> uninitialized
variables, the value of the user's
> storage and can
be used as the basis for a Trojan
> Horse.
Through the use of C obfuscation (the
> deliberate use
of legacy features, designed by C's
> inventors for
their convenience in programming a DEC
> PDP-10) this
code's intent can be fully concealed.
> It is possible
to write Word and Exchange macros in
> VBA, a form of
Visual Basic, but Trojan code as
> added to a VB
application is much harder to "obfuscate."
It is certainly true that the code can readily be changed, but again
that is a version control issue, not a C programming language issue.
> 5. The code will not parse any string
that contains a NUL
> character, nor
is it at all clear how to hack it to do
> so. The
functions that start with str have to be replaced
> with different
functions, just for starters. In the
> real world, this
means that the presence of NULs or
> international
characters will cause the code to be
> discarded and
rewritten.
I think it will be discarded long before then. Note: your ignorance
of
wchar_t does not constitute a failing on the part of the C language.
I
do agree, however, that support for wide characters is minimal
in C90.
This has been fixed in C99.
> 6. One of the most common uses of
this kind of parser is, as
> noted above,
a For loop which scans left to right. The
> solution is useless
for high-intensity packing of long
> strings (despite
the reputation of C for "efficiency")
> because such
common For loops will repeatedly scan the
> same string,
multiplying their time complexity.
To me, that simply shows that your "solution" is flawed.
> The only way to
get around this problem is "hacking"
> an ugly spoof
of the initial version of the VB solution
> which as noted
above uses an array in the object state.
> Essentially,
this is a rewrite of the solution, not a
> reuse at all.
There is another way around the problem, which is to write a decent
solution in the first place.
> 7. The C solution only returns one
thing, and that is the
> value of the
item. The itempars function of the C
> solution knows,
and promptly forgets, the starting
> index of the
item although this is a common need in
> parsing.
To add it, the C solution would need either
> an extra function
or an additional parameter in
> itempars.
Furthermore, this functionality would still
> be not available
to C users who go through the "higher
> level" functions
wordpars, etc, to get predefined
> syntaxes.
So what you seem to be saying is that you got your design wrong.
Have
you ever heard of structs?
> 8. Perhaps the most dramatic disadvantage
of the C solution
> is that because
it implements functionality and not an
> object
[ Here, you use the word "object" in an OO context. ]
If that's what you really want, C can do it for you. But C++ is
better
suited to that kind of thing. Of course, it's not really a disadvantage
to provide functionality - it's simply a different way of looking
at
programming.
> (for an object is not a named region of storage)
Well, yes it is. Strictly, in C, it is a "region of data storage
in the
execution environment, the contents of which can represent values".
> it is unable to modify strings in terms of their parse syntax,
> unlike the VB solution, and it does not provide a firm basis
> for this change. The C solution is in no way an intellectual
> resource for the programmer tasked with modifying objects.
Again, this is a hallmark of your "solution", not a limitation of
the C
programming language.
> In C this is addressed by writing an unrelated function,
> sharing at best the verbiage of the existing code's comments
> and at worst (as in the C library example of strstr versus
> other tools) imposing a completely different mental
> structure on the task.
At worst as in strstr? There's nothing particularly wrong with strstr
as
far as I'm aware.
> 9. Unlike the VB object, the C solution is useless
in
> industry.
I can't speak for the VB object, but I can certainly agree that
I
wouldn't touch your C solution with a bargepole.
> It is too slow and has to be fully understood by the
> reuser, who has to be willing to free every pointer
> returned by its functions.
Then design it better. FCOL.
> Note how I have completely avoided, in this list of C drawbacks,
> any appeal to programmer skill.
That's just as well.
> Strawman? Apples and Oranges?
>
> Before we get to the conclusion, we need to carefully review
> the possibility that we've presented a flawed or limited C
> program and compared it to an excellent VB solution.
I can't speak for the VB solution. My time is not unlimited, and
your
article was exceedingly long.
> I think that the C program, as C, is not flawed or limited.
I disagree. I think it is quite seriously flawed.
> Tasked with the goal of getting a string, it addresses
> this task straightforwardly.
If that is your solution's only task, then why do you criticise
it so
heavily for not achieving lots of other things too? If those other
things are also required, then the fact that your C program does
not
achieve them is an indication of a flaw in your program.
> A major limitation in the strxxx functions is that they do not
> handle strings containing the NUL character [...]
In C, a string is defined as follows: "A string is a contiguous
sequence
of characters terminated by and including the first null character."
It
is hardly surprising that the strxxx functions treat a null character
as
a string terminator - they are required to by the Standard. Thus,
if you
must have null characters in your data, clearly you must deal with
these
yourself. C'est la vie. I don't see this as a major limitation.
It has
certainly never caused me serious problems.
> If the problem had been to process records, the C program would
> have dealt at a low level, not with objects or even records
> but with unsafe pointers to the beginning of records.
Pointers are only unsafe if you don't use them properly. If you
can't
use pointers properly, I would recommend that you either learn
how to
use them properly or, if you're not prepared to do that, stay a
long way
away from C. But that's not a limitation of the C programming language.
Pointers can be used safely, and are used safely, by competent
C
programmers.
> The C program could mimic the VB program by allocating
> in its open text a malloc'd array of items.
Or of course you could have written the C program first, and done
this
in the C program first, in which case the VB program would have
been
imitating the C program. I'm curious as to why you adopted different
designs for the two languages.
> The problem here is that this array's scope would be
> any function added to the run unit containing the
> original C functions.
<shrug> It's just an object. Managing it is not difficult.
> The problem is that it's hard, in a practical sense, to
> make C functions stateful because the association between
> the function and its state is enforced only in the
> mind of the original developer.
More to the point, it's generally considered a bad idea for C functions
to retain state between invocations. It's far better for objects
to
retain state, and for functions to be handed "state-on-a-plate",
state
which they can use (and perhaps alter) appropriately and then forget
about; it isn't their job to remember.
Indeed, you go on to say of your VB solution:
> If the object needs to be stateless, this state can be
> passed to the object
and a similar arrangement is true in C - if the function needs to
be
stateless, this state can be passed to the function.
> My C solution, I believe, shows a solution which is trying and failing
You got *that* right.
> to reify what is really needed, and that is a string with a
> grammar. The illusion that it is is a legend in the
mind
> of the programmer which can never be anything else.
If your solution tries and fails, is it really a solution? It strikes
me
as more of a complete waste of time.
> If we merely, and crudely, score my lists of advantages,
> subtracting a point for a disadvantage and adding a point
> for an advantage, C's score is 5-9 or –4 and VB's score is
> 11-4 or 7. Based on this and more important on my code
and
> narrative, VB is superior for this problem (thought to be
> part of C's turf) by almost an order of magnitude.
How ludicrous. Your C code is indeed deeply flawed - by design.
Your
design is poor and your implementation is poor. Don't blame the
C
language for your own shortcomings.
> Note that Visual Basic here trespasses on string parsing in a
> way that may be resented by both Perl and by C programmers,
> who feel their language is more natural in parsing.
This is ludicrous again. Firstly, if you can do cool parsing in
VB,
that's great and I'm very happy for you, and I'm sure Perl programmers
would agree with me that There's More Than One Way To Do It - a
workable
VB string parser would be a wonderful thing.
Secondly, even if that weren't the case, why should a Perl programmer
or
a C programmer resent the fact that a VB programmer trumpets a
VB parser
as superior to a C parser, when that VB programmer's comparison
is based
on his own poor C program?
> Nonetheless, the first VB version is clearly more reusable,
> more easy to describe and even more efficient for
> high-intensity parsing than the first C version.
This says a lot more about you than it does about C. Note that a
properly written C solution will be much more re-usable than a
VB
solution, simply because it can be put to work in so many more
environments. VB programs are very much restricted to PCs running
Windows (or, a few years ago, MS-DOS).
> I have spent quite a lot of time on this problem. That's
because
> I don't feel it is ethical to make the claim that OO design using
> OO languages is useless
Who has made this claim?
> and I don't feel it is ethical to claim that C can be used
> for new development.
Fine. I don't agree, but you're quite entitled to feel that way.
Personally, I use C for new developments on quite a regular basis,
albeit not /all/ the time.
>
> These claims are unethical in addition to being technically wrong
The first wasn't even made as far as I can recall, and the second
is not
technically wrong *at all*.
> because they generalize
> from a self-interested specialization in a programming language
to
> advocating its general
> use.
Whatever works. If C works for you, use it. Clearly, it doesn't,
and
frankly I'm not surprised. But it does work for me, so I'll carry
on
using it whenever it's the most appropriate tool for the job, which
is
quite often.
> Note that I do not do this for Visual Basic.
Just as well, since Linux programmers would have a hard time otherwise.
> I think technicians themselves need to start a technical
> narrative of how apparently value-free decisions and
> recommendations, such as the use of C (coupled with
> blame the victim narratives of its failings which ascribe
> its failings to incompetence) encapsulate a contempt for labor.
This appears to be a rebuttal of "the poor workman blames his tools".
Well, I think the adage is spot on, irrespective of your verbose
rebuttal, but I think I can see why you are rebutting it. (It's
called
"self-defence".)
> There are VB environments in which creating an object is a
> termination offense, and perhaps inspire by Mr Heathfield,
> managers shall make coding in C++ as opposed to C
> another termination offense.
Why should they? C++ is a great language. In fact, I've recently
successfully completed work on a C++ project, along with a colleague.
We
chose C++ because that was her preferred language and we both agreed
that it was easier for a C programmer to adapt to extra toys than
for a
C++ programmer to adapt to fewer toys. I certainly prefer C to
C++, but
that doesn't mean I don't like C++.
> As a result, Mr. Heathfield's technically excellent book on C
contains
> two major errors.
Actually, it contains far more than two, I'm afraid, but let's see
whether it has the two you think it has.
> An object is not "a named region of storage",
Sure it is - in C. (I won't give you the official definition for
a third
time - just scroll up.) So that's *not* a major error, or even
a minor
error.
> it is instead, like
> powerString, a tightly
> coupled interaction of specific problem data and code that, in
the
> object instantiation, can
> focus only on one state and set of problem data.
Those can be objects too, yes.
> Nor is it a good idea to use C in a new applications system.
Sure it is. It's a great idea. So that's not an error either. Good.
> [...] I simply cannot see how a faithful reader of the
> technical press, outside of Maxim and The C Programmer's Journal,
> could not help but know that an object is not a named region
of
> storage.
Does "The C Programming Language", 2nd edition, by Kernighan and
Ritchie, qualify as "the technical press"?
> I have shown by actual construction that the object solution to
the
> problem I have set myself, even though it uses a language without
> inheritance, single or multiple, provides a qualitatitively
> different solution which solves not only the problem of retrieving
> items but also the problem of changing their values in the string.
That's nice. I haven't read your VB code.
> I have shown by actual construction that the corresponding C
> program is a complete and utter toy.
You have shown, by actual construction, that you're not terribly
good at
C programming. That's more or less all you've shown.
> The worker's satisfaction in a job well done usually lies in
> beholding the result completely outside of his mind. The
> problem with the claim of Turing equivalence (Mr. Heathfield's
> repeated insistence that "you can do that in C and furthermore
> you are a moron if you don't agree") [...]
Please source this quotation, or retract it. I don't recall saying
any
such thing.
**********
*
*
At or shortly after this point in your article, you included your
C code
and your VB code. I will not comment on your VB code, which I have
not
read. I am prepared to comment on your C code, but not right now.
I will
do so in a separate article, which I will post at or about this
part of
the thread, as soon as the critique is ready.
*
*
**********
> > Which was Monday. He's written nothing since Sunday, so
he's either
> > busy coding or decided to give it up.
>
> No, I was busy working. Thrashing you and Richard,
When does that start? So far, it's been all the other way.
> unfortunately (in
> of course a metaphorical and sporting sense), is for me like
following
> hounds, or Mah Jongg, not something for which I get paid.
I'd
> completed the code on Sunday but since I choose to keep Internet
> activities sharply separated, I can post it today.
>
> I am thinking about coming out with a new edition of the above
after
> you fellows have had a chance to review it, and indeed replace
the
> parse example by a truth table example that will parse, in C
and VB, a
> logical expression. Do let me know what you think.
Hammering you
> chaps into the long grass is after all only a sideline.
When will you start this hammering?
> The main goal
> is truth. As such I regard you and Mr Heathfield as comrades
*malgre
> lui*.
I agree that the main goal is truth, and the truth is that C is
just
fine for cutting new apps. Other things are fine too, of course,
but C's
certainly on the list.
--
Richard Heathfield : binary@eton.powernet.co.uk
"Usenet is a strange place." - Dennis M Ritchie, 29 July 1999.
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
K&R answers, C books, etc: http://users.powernet.co.uk/eton
Message-ID: <3D1A361C.D393D56B@eton.powernet.co.uk>
Date: Wed, 26 Jun 2002 22:46:04 +0100
From: Richard Heathfield <binary@eton.powernet.co.uk>
Organization: Eton Computer Systems Ltd
X-Mailer: Mozilla 4.6 [en-gb]C-CCK-MCD NetscapeOnline.co.uk
(WinNT; I)
X-Accept-Language: en-GB,en
MIME-Version: 1.0
Newsgroups: comp.programming
Subject: Re: The Data Quality Act
References: <f5dda427.0206031634.d378860@posting.google.com>
<3D1301C4.CFC87CDE@eton.powernet.co.uk> <f5dda427.0206211823.636ff86@posting.google.com>
<3D14865C.54B206A1@eton.powernet.co.uk> <f5dda427.0206221613.725aae18@posting.google.com>
<3D1579EB.B9B92133@eton.powernet.co.uk> <f5dda427.0206231131.36233992@posting.google.com>
<3D165D5D.C17DCDAF@eton.powernet.co.uk> <a78137c0.0206240445.5ce183ed@posting.google.com>
<3D184267.37D5051D@eton.powernet.co.uk> <3D18AC3E.A9FA9A80@mmm.com>
<f5dda427.0206251750.7d4f9c1@posting.google.com>
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
NNTP-Posting-Host: 195.60.5.6
X-Trace: news.power.net.uk 1025127807 195.60.5.6 (26 Jun 2002 22:43:27
+0100)
Lines: 995
Path: news.power.net.uk
Xref: news.power.net.uk comp.programming:80389
Well, I have some good news and some bad news. First, the good news
- it
appears that my news client *can* handle large articles if run
on a more
powerful operating system than I used for my previous reply to
this
article.
Now, the bad news. I have yet to post my review of Mr Nilges' C code.
Oh well, here we go...
"Edward G. Nilges" wrote:
>
<big old snip>
> The C Program
>
> The C program is one file, itempars.C, which can probably be
run on
> any C or C++
> system. For best results, use your compiler to create an
executable
> without command line
> arguments, run the executable, and make sure that it announces
results
> that are identically
> displayed to the immediate left of the expectation. If
you like, add
> code to the main
> function to make further tests and let me know if you find any
bugs,
> or have a proposed
> hack.
I've retained the entire program for the purposes of this review.
I
don't know whether that's a good idea or not, and it's easier to
avoid
deciding. :-)
Please note that, in the event of a review comment applying to more
than
one place in the code, I have only added the comment at the first
instance. That doesn't mean the review comment doesn't also apply
to the
other instances, of course.
>
> /**********************************************************************/
> /*
> */
> /*-------STRING PARSING: COMPARATIVE EXAMPLE FOR THE C
> LANGUAGE-------*/
Well, we've hit our first problem already. Your comment layout is
fragile, and awkward to maintain. Consider dropping your right-hand
box
edge. For example, one common style
/*******************************************
*
* looks like this, It retains your box
* concept, but doesn't dissuade maintainers
* from changing comments when it becomes
* necessary so to do.
*
******************************************/
That's not the style I use, but it's more sensible than yours.
> /*
> */
> /* This is a set of C functions that show reasonably good practice,
Do try to keep your comments accurate.
> in*/
> /* developing a parser for items delimited by sets of alternative
> */
> /* characters or by specific, possibly multicharacter strings.
> */
> /*
> */
> /* This code was written as part of the discussion between Richard
> */
> /* Heathfield (author of C Unleashed) and Edward G. Nilges on
the
> */
> /* merits of C versus an OO language, and it can be compared
to the
> */
> /* stringParsing.VBP Visual Basic project by interested readers.
> *
> /*
> */
> /* In addition to a main() test driver, the following functions
are
> */
> /* provided:
> */
> /*
> */
> /*
> */
> /* * freeNonnull: free non-null
string pointers
> */
> /* * itempars: retrieve parsed
tokens
> */
> /* * items: count parsed
tokens
> */
> /* * listpars: retrieve comma-delimited
list items
> */
> /* * listitems: count comma-delimited
list items
> */
> /* * linepars: retrieve newline-delimited
items
> */
> /* * lines: count newline-delimited
items
> */
> /* * wordpars: retrieve comma-delimited
list items
> */
> /* * words: count blank-delimited
words
> */
> /* * string2Display: string
to displayable value
> */
> /*
> */
> /*
> */
> /**********************************************************************/
>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
>
> /**********************************************************************/
> /*
> */
> /* string2Display String to displayable
value
> */
> /*
> */
> /* This function may be passed NULL or a pointer to a string.
It
> */
> /* returns the unquoted string NULL or the quoted string. Note
that
> it*/
> /* allocates a new memory block for either return value.
If this
> */
> /* allocation fails this function returns the value NULL.
> */
> /*
> */
> /**********************************************************************/
> char *string2Display(char *strInstring)
Two problems here, one technical and one stylistic.
The technical problem is that your function name invades implementation
namespace, as does any external identifier beginning with str followed
immediately by a lower case letter.
The stylistic problem is obviously less serious, but still worth
noting:
string2Display suggests, at least to me, that there ought to be
a
string0Display() function somewhere in the vicinity, and possibly
a
string0Display(), string3Display(), etc. Furthermore, it doesn't
describe terribly well what the function is intended to achieve.
This
function seems to do two things, in fact - (1) make a duplicate
of the
string, and (2) put quotation marks around it. You could reasonably
claim, however, that it does only one thing - it makes a quoted
duplicate of the string. In that case, why not call it something
like
quoted_dupstr?
The function itself, like much of your writing, is very dense, and
thus
harder to read than it need be. Better use of vertical space could
make
it much more readable.
> {
> int intLength;
Since this variable is used for storing (a derivative of) the length
of
a string, and for passing to malloc, it should really be of type
size_t.
Of course, making it size_t would be awkward for you, because you've
(IMHO rather foolishly) nailed its type to its name. There's no
need for
this. It's a relatively short function, and the type is clearly
visible
in the definition statement. Even if this were a very long function,
modern IDEs are quite good at taking you straight to a definition,
often
with a single keystroke, and returning you to your original context
with
another single keystroke. (And, on a hard copy, it's no hardship
to
glance up to the top of the function for a moment, especially since
you've gone to such trouble to delineate the function's beginning
with a
large comment block.)
> char *strOutstring;
> intLength = strInstring == NULL ? 4 :
strlen(strInstring) + 2;
This is needlessly complicated, and forces a diffident maintenance
programmer to reach for his precedence table. Better:
if(NULL == strInstring)
{
intLength = sizeof "NULL";
}
else
{
intLength = strlen(strInstring)
+ sizeof "\"\"";
}
This also eliminates the need for your + 1 in the malloc call below.
Note how the use of sizeof helps in readability by taking away
magic
numbers and replacing them with meaningful and expressive code.
This is
called "self-documenting code".
> strOutstring = (char *)malloc( intLength + 1 );
The cast is unnecessary. Since you remembered to include the correct
header for malloc(), no harm is done on this occasion, but the
unnecessary code obfuscates the code for no benefit.
> if ( strOutstring == NULL ) return(strOutstring);
I see no benefit to putting protasis and apodosis on the same physical
line. My personal preference is always to use a compound statement
after
an if, do, for, etc, even where the syntax does not require it,
as it
tends to make maintenance programmers less nervous, but this is
a style
point with which some clueful people disagree, so I'm merely mentioning
it as an aside. Similarly, I don't favour having more than one
exit
point from a function (or loop), but again that's a style issue
with
which some clueful people disagree.
> if ( strInstring == NULL )
> { strcpy( strOutstring,
"NULL" ); return(strOutstring); }
There's no advantage to putting the strcpy and return on the same
physical line.
> strOutstring[0] = '\"';
The backslash escape character is optional here.
> memcpy( strOutstring + 1, strInstring,
intLength - 2 );
> strOutstring[intLength - 1] = '\"';
> strOutstring[intLength] = '\x00';
Possible alternatives:
a:
sprintf(strOutstring, "\"%s\"", strInstring); /* this line
replaces
all four lines */
b:
strOutstring[0] = strOutstring[intLength] = '"';
strcpy(strOutstring + 1, strInstring);
/* these two lines
replace the four */
> return(strOutstring);
These parentheses are unnecessary. They contribute nothing, and
detract
from readability.
> }
>
> /**********************************************************************/
> /*
> */
> /* freeNonNull Free non-null string pointer
> */
> /*
> */
> /**********************************************************************/
> void freeNonNull( char * strPtr )
> {
> if ( strPtr == NULL ) return;
> free( strPtr );
> }
This function can be replaced by:
void freeNonNull(char * strPtr)
{
free(strPtr);
}
This is because free() is required to handle the NULL case correctly
(by
simply returning). Thus, you are pointlessly duplicating standard
library functionality.
Once you realise this, you can drop the function altogether.
>
> /**********************************************************************/
> /*
> */
> /* itempars Parse items
> */
> /*
> */
> /* This function returns substrings of an input string (known
as
> */
> /* items) which are delimited in one of two ways:
> */
> /*
> */
> /*
> */
> /* * Set-delimited items
are delimited by one or more
> */
> /*
characters from a set of alternatives
> */
> /*
> */
> /* * String-delimited items
are delimited by a specific string,
> */
> /* which may
contain more than one character.
This design is poor. You have two very distinct tasks here, and
they
would both have been better served by being allotted to separate
functions.
> */
> /*
> */
> /*
> */
> /* The word is returned as a copy of the source word in its own
> */
> /* allocated area. A return of NULL indicates an error
including
> */
> /* failure to allocate the required storage.
> */
> /*
> */
> /* The string for parsing is passed in strInstring: the index
of the
> */
> /* required item is passed in intIndex. The set of alternative
> */
> /* single-character delimiters is passed in strDelimiter.
The
> */
> /* intStringDelimiter parameter should be -1 if the item is string-
> */
> /* delimited as described above, or anything else (by convention,
0)
This comment ("anything else") is false. For set-delimiting, the
value
*must* be 0. You have got it exactly wrong, in fact. If the value
is 0,
the code assumes set-delimiting, and any other value will cause
the code
to assume string-delimiting.
> */
> /* when the item is set-delimited, as described above.
> *
> /*
> */
> /* Note: if the index is less than one or beyond the end of the
> string*/
> /* (for example, if the fourth blank-delimited item of "Moe Larry
> */
> /* Curley" is requested) then this function returns NULL.
Thus, you have two different causes for a NULL return. The first
is for
a system problem - insufficient memory - and the second is a data
problem - a request for a token that does not exist. How is the
caller
to distinguish between these two?
> */
> /*
> */
> /* Note: the input string cannot include the Nul character.
A string is terminated by a null character, so it's not surprising
really that it can't include one. If you want to embed null characters
in your data, don't use C strings. Use blocks of memory instead.
> *
> /*
> */
> /*
> */
> /**********************************************************************/
> char *itempars
Why itempars? Why not itemparse, or ItemParse, or ParseItem?
> (char *strInstring,
Since you have no intention of writing to this pointer, I suggest
you
make it const.
> int intIndex,
Since the index cannot logically be negative, it would be better
to use
an unsigned type here. size_t springs to mind.
> char *strDelimiter,
Since you have no intention of writing to this pointer, I suggest
you
make it const.
>
int intStringDelimiter)
> {
> int intIndex1 = 0, intIndex2 = 0, intCount
= 0;
These would be better as size_t too; it would mean you were no longer
mixing signed with unsigned arithmetic.
> int intLength;
> char *strNew;
> if ( intIndex <= 0 ) return(NULL);
> while ( intIndex1 < strlen(strInstring)
)
This is very poor style. You call strlen on *every* iteration of
the
loop. Premature optimisation is of course the root of all evil,
as Knuth
showed us, but on the other hand (Bentley adds), we cannot ignore
efficiency. Get the string's length *once only* before the loop
starts,
saving the result in a temporary variable for use in the loop control
statement.
Your next code section is practically illegible...
> {
> if ( intStringDelimiter
)
>
{ intIndex2 = (int)strstr(strInstring + intIndex1,
> strDelimiter)
>
-
>
(int)strInstring; }
> else {
>
intIndex1 = strspn(strInstring + intIndex1, strDelimiter)
> + intIndex1;
>
if ( intIndex1 <= strlen(strInstring) )
>
intIndex2 = strcspn(strInstring + intIndex1,
> strDelimiter) + intIndex1;
>
else intIndex2 = intIndex1;
> }
> if ( ++intCount
== intIndex ) break;
> intIndex1 = intIndex2
+
> (intStringDelimiter?strlen(strDelimiter):1);
> }
Here it is again, re-flowed for readability.
> while ( intIndex1 < strlen(strInstring) )
We've dealt with this already.
> {
> if ( intStringDelimiter )
This is the line that breaks your comment. The expression will yield
false if and only if intStringDelimiter's value is 0.
> {
> intIndex2 =
> (int)strstr(strInstring
+ intIndex1, strDelimiter) -
> (int)strInstring;
Here we have a major portability headache. Whilst it is true that
you
can convert pointers into ints, the result of doing so is
implementation-defined and is not guaranteed not to lose information.
Not only that, but there's no point to it. You could simply have
omitted
the casts, and had perfectly well-defined behaviour.
> }
> else
> {
> intIndex1 =
> strspn(strInstring
+ intIndex1, strDelimiter) +
> intIndex1;
>
> if ( intIndex1 <= strlen(strInstring)
)
You see, if you'd saved this value, you could have used it here
instead
of recalculating it.
> {
> intIndex2 =
> strcspn(strInstring
+ intIndex1, strDelimiter) +
> intIndex1;
> }
> else
> {
> intIndex2 = intIndex1;
> }
> if ( ++intCount == intIndex
)
> {
> break;
If you must exit from a loop early, it's a good idea to document
why you
are doing so, and what you plan to do next.
> }
>
> intIndex1 = intIndex2 +
> (intStringDelimiter?strlen(strDelimiter):1);
> }
Okay, that's the end of the while loop.
> if ( intIndex1 >= strlen(strInstring) ) return(NULL);
Do you see how much you use this value?
> if ( ( strNew = (char *)malloc( intLength
= intIndex2 - intIndex1)
> ) == NULL )
> return(NULL);
> memcpy( strNew, strInstring + intIndex1,
intLength );
> strNew[intLength] = '\x00';
FYI The normal C idiom for the null character is '\0'.
> return( strNew );
> }
>
> /**********************************************************************/
> /*
> */
> /* items Return the number of items
> */
> /*
> */
> /* This function returns the count of delimited substrings (items)
in
> */
> /* a string: cf itempars for details on item syntax.
> */
> /*
> */
> /* strInstring should be a NUL delimited string and strDelimiter
a
> */
> /* set of delimiter characters or a fixed delimiter, as described
> */
> /* in itempars. intStringDelimiter should be -1 when strDelimiter
is
> */
> /* a fixed delimiter or anything else (by convention 0) when
> */
> /* strDelimiter is a set of alternative delimiter characters.
> */
> /*
> */
> /**********************************************************************/
> int items(char *strInstring,
>
char *strDelimiter,
Since you have no intention of writing to these pointers, I suggest
you
make them const.
>
int intStringDelimiter)
> {
> int intCount = 0;
> int intIndex1 = 1;
> char *strNext;
> for ( ;
> ((
strNext = itempars(strInstring,
>
intIndex1,
>
strDelimiter,
>
intStringDelimiter) )
>
!=
>
(char *)NULL);
The cast is unnecessary.
> intIndex1++,intCount++
) { free(strNext); };
> return(intCount);
What a strange function. It allocates memory only to throw it away
again
without even looking inside.
Since you can't have a negative number of items, a size_t would
have
been more appropriate for the return value.
> }
>
> /**********************************************************************/
> /*
> */
> /* wordpars Parse blank-delimited words
> */
> /*
> */
> /* This function returns the string containing the nth
> blank-delimited*/
> /* word in strInstring, where intIndex is n starting at 1 for
the
> */
> /* leftmost word.
> */
> /*
> */
> /* The word is returned as a copy of the source word in its own
> */
> /* allocated area. A return of NULL indicates an error
including
> */
> /* failure to allocate the required storage.
> */
> /*
> */
> /* The string for parsing is passed in strInstring: the index
of the
> */
> /* required item is passed in intIndex.
> */
> /*
> */
> /* Note: if the index is less than one or beyond the end of the
> string*/
> /* (for example, if the fourth blank-delimited word of "Moe Larry
> */
> /* Curley" is requested) then this function returns NULL.
> */
> /*
> */
> /* Note: the input string cannot include the Nul character.
> */
> /*
> */
> /*
> */
> /**********************************************************************/
> char *wordpars(char *strInstring, int intIndex)
Make this pointer const.
> {
> char *strWord;
> strWord = itempars(strInstring, intIndex,
" ", 0);
> return(strWord);
> }
Why bother with the temp?
return itempars(strInstring, intIndex, " ", 0);
would have been more straightforward. In fact, was this function,
or any
of the next several functions, worth the trouble of writing?
>
> /**********************************************************************/
> /*
> */
> /* words Count blank-delimited words
> */
> /*
> */
> /* This function returns the number of blank-delimited words
in its
> */
> /* string argument strInstring.
> */
> /*
> */
> /*
> */
> /**********************************************************************/
> int words(char *strInstring)
This is a very poor choice of function name (like most of your function
names). It doesn't describe what the function does.
> {
> return (items(strInstring, " ", 0));
> }
>
> /**********************************************************************/
> /*
> */
> /* listpars Parse comma-delimited list items
> */
> /*
> */
> /* This function returns the string containing the nth
> comma-delimited*/
> /* item in strInstring, where intIndex is n starting at 1 for
the
> */
> /* leftmost item.
> */
> /*
> */
> /* The list item is returned as a copy of the source item in
its own
> */
> /* allocated area. A return of NULL indicates an error
including
> */
> /* failure to allocate the required storage.
> */
> /*
> */
> /* Note: if the index is less than one or beyond the end of the
> string*/
> /* (for example, if the fourth blank-delimited item of "Moe,Larry,
> */
> /* Curley" is requested) then this function returns NULL.
> */
> /*
> */
> /* Note: the input string cannot include the Nul character.
> */
> /*
> */
> /*
> */
> /**********************************************************************/
> char *listpars(char *strInstring, int intIndex)
> {
> char *strItem = itempars(strInstring,
intIndex, ",", -1);
> return(strItem);
> }
>
> /**********************************************************************/
> /*
> */
> /* listitems Count comma-delimited items
> */
> /*
> */
> /* This function returns the number of comma-delimited words
in its
> */
> /* string argument strInstring.
> */
> /*
> */
> /*
> */
> /**********************************************************************/
> int listitems(char *strInstring)
> {
> return (items(strInstring, ",", -1));
> }
>
> /**********************************************************************/
> /*
> */
> /* linepars Parse newline-delimited items
> */
> /*
> */
> /* This function returns the string containing the nth newline-
> */
> /* delimited item in strInstring, where intIndex is n starting
at 1
> */
> /* for the first item.
> */
> /*
> */
> /* The line is returned as a copy of the source item in its own
> */
> /* allocated area. A return of NULL indicates an error
including
> */
> /* failure to allocate the required storage.
> */
> /*
> */
> /* Note: if the index is less than one or beyond the end of the
> string*/
> /* (for example, if the fourth line of "Moe\nLarry\nCurley" is
> */
> /* requested then this function returns NULL.
> */
> /*
> */
> /* Note: the input string cannot include the Nul character.
> *
> /*
> */
> /*
> */
> /**********************************************************************/
> char *linepars(char *strInstring, int intIndex)
> {
> char *strItem = itempars(strInstring,
intIndex, "\n", -1);
> return(strItem);
> }
>
> /**********************************************************************/
> /*
> */
> /* lines Count lines
> */
> /*
> */
> /* This function returns the number of newline-separated lines
in its
> */
> /* string argument strInstring.
> */
> /*
> */
> /*
> */
> /**********************************************************************/
> int lines(char *strInstring)
> {
> return (items(strInstring, "\n", -1));
> }
>
> int main(int argc,char *argv[])
You never use argc and argv, so why bother to include them? The
Standard
allows another form of main(), int main(void), which is tailor-made
for
situations like this.
> {
> char strStooges1[100];
> char *strP1;
> char *strP2;
> /* Test wordpars with normalized string
*/
> strStooges1[0] = '\x00';
Personally, I would feel safer with the whole array being initialised,
e.g. via an initialisation: char strStooges[100] = {0};
> fprintf( stdout,
>
"Expect 0: %d\n",
>
words(strStooges1) );
> strcpy(strStooges1, "Moe Larry Curley
Shemp Curley-Joe
> CurleyJoeBesser");
This linewrap caused a compilation failure. I'm not complaining
- it
happens sometimes on Usenet - but I thought I'd mention it for
future
reference. You can obviate this problem very easily, using string
literal merging:
strcpy(strStooges1,
"Moe Larry Curley Shemp Curley-Joe"
" CurleyJoeBesser");
> fprintf( stdout,
>
"Expect 6: %d\n",
>
words(strStooges1) );
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 0))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Moe\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 1))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Larry\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 2))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Curley\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 3))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Shemp\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 4))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"CurleyJoe\": %s\n",
Error in test data. You should be expecting "\"Curley-Joe\"". Note
the
hyphen.
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 5))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"CurleyJoeBesser\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 6))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 7))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> /* Test wordpars with unnormalized string
*/
> strcpy(strStooges1,
>
" Moe Larry Curley Shemp
Curley-Joe
> CurleyJoeBesser");
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 0))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Moe\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 1))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Larry\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 2))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Curley\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 3))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Shemp\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 4))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"CurleyJoe\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 5))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"CurleyJoeBesser\": %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 6))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = wordpars(strStooges1, 7))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> /* Test listpars */
> strcpy(strStooges1,
> ",Moe,,Larry,Curley,,Shemp,Curley-Joe,CurleyJoeBesser,");
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 0))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 1))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Moe\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 2))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 3))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Larry\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 4))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Curley\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 5))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 6))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect \"Shemp\": %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 7))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> fprintf( stdout,
>
"Expect NULL: %s\n",
>
strP2 = string2Display(strP1 = listpars(strStooges1, 20))
> );
> freeNonNull(strP1); freeNonNull(strP2);
> return 0;
> }
>
Some design suggestions:
Separate out the parsing of string-delimited strings from set-delimited
strings.
Instead of items() calling itempars(), you could consider writing
a
function whose job is to provide the start and end points of a
given
token.
struct TOKEN_LOCATION
{
size_t begin;
size_t end;
};
int find_token(struct TOKEN_LOCATION *loc, const char *inString,
const
char *delimiters);
Then, items() could simply call this in a loop, thus requiring no
allocation at all. Furthermore, itempars() could call it instead
of
doing its own arithmetic. Or, if you prefer, you could write one
function to do the parsing and store the results in a struct containing
a dynamic array of TOKEN_LOCATIONs and a count. (In other words,
do all
the tokenisation in one go.)
Verdict: You have indeed forgotten a lot about C programming, haven't
you? Don't call us. We'll call you...when <insert proverbially
distant
event here>.
<another big old snip>
--
Richard Heathfield : binary@eton.powernet.co.uk
"Usenet is a strange place." - Dennis M Ritchie, 29 July 1999.
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
K&R answers, C books, etc: http://users.powernet.co.uk/eton