This page includes two Usenet articles, both of which were responses to Mr Nilges' attempt at C programming.
 

Jump to the code review
 

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
 
 

back to top
 

Code review

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
 

back to top