C Programming Language 2nd Ed, Exercise 1.9 and 1.12, Solution suggestion.

  • Thread starter Christos Kokaliaris
  • Start date

C

Christos Kokaliaris

Greetings everybody,

I am a Mathematics graduate and I need C in order to integrate it with R and other languages I use for statistics and simulation. I took C when undergraduate but it wasn't enough. Recently I decided to study analytically the classic book and solve every single exercise. After comparing my own codewith the already suggested in the archives of clc-wiki, I thought of posting my solutions.

Criticism and suggestions on the quality of the solutions I suggest are much needed and most welcome.

Thank you for reading.

// ex1-9
#include <stdio.h>

/* copy input into output, replacing
one or more blanks by a single blank */
main()
{
int c, nb=0;

while ((c=getchar()) != EOF) {
/* if char is blank increase counter else reset counter.
this notation will be mentioned later in the book
(T) ? A : B; if TRUE then A else B */
(c == ' ') ? (++nb) : (nb = 0);
if (c != ' ' || nb < 2) putchar(c);
}
}

//ex1-12
#include <stdio.h>

#define IN 1
#define OUT 0

// print input one word per line
main()
{
int c, state;
// start without a word
state = OUT;

while ((c = getchar()) != EOF) {
// if the char is not blank, tab, newline
if (c != ' ' && c != '\t' && c != '\n') {
// inside a word
state = IN;
putchar(c);
}
// otherwise char is blank, tab, newline, word ended
else if (state == IN) {
state = OUT;
putchar('\n');
}
}
}
 
Ad

Advertisements

S

Stefan Ram

Christos Kokaliaris said:
I am a Mathematics graduate and I need

to limit the length of my lines to the length
that is common in Usenet posts. To learn more
about this, I need to become familiar with
Usenet FAQs and clients (Newsreaders).
I decided to study analytically the classic book
and solve every single exercise.

That sounds very good. When you are done with
»The C Programming Language«, then do the same
with »The Art Of Computer Programming«! ;-)
/* copy input into output, replacing
one or more blanks by a single blank */
main()

It has to be »int main()«. »main()« without
»int « is archaic. If »main« is declared this
way in in you book, then the book is too old.
 
C

Christos Kokaliaris

Greetings everybody,

I am a Mathematics graduate and I need
C in order to integrate it with R and
other languages I use for statistics and
simulation. I took C classes when
undergraduate but it wasn't enough.
Recently I decided to study analytically
The "C programming Language 2nd Ed" book
and solve every single exercise.
After comparing my own code with the
already suggested in the archives
of clc-wiki, I thought of posting my
solutions.

Criticism and suggestions on the
quality of the solutions I suggest
are much needed and most welcome.

Thank you for reading.

// ex1-9
#include <stdio.h>

/* copy input into output, replacing
one or more blanks by a single blank */
main()
{
int c, nb=0;

while ((c=getchar()) != EOF) {
/* if char is blank increase counter else reset counter.
this notation will be mentioned later in the book
(T) ? A : B; if TRUE then A else B */
(c == ' ') ? (++nb) : (nb = 0);
if (c != ' ' || nb < 2) putchar(c);
}
}

//ex1-12
#include <stdio.h>

#define IN 1
#define OUT 0

// print input one word per line
main()
{
int c, state;
// start without a word
state = OUT;

while ((c = getchar()) != EOF) {
// if the char is not blank, tab, newline
if (c != ' ' && c != '\t' && c != '\n') {
// inside a word
state = IN;
putchar(c);
}
// otherwise char is blank, tab, newline, word ended
else if (state == IN) {
state = OUT;
putchar('\n');
}
}
}
 
K

Kaz Kylheku

Greetings everybody,

I am a Mathematics graduate and I need C in order to integrate it with R and
other languages I use for statistics and simulation. I took C when
undergraduate but it wasn't enough. Recently I decided to study analytically
the classic book and solve every single exercise. After comparing my own code
with the already suggested in the archives of clc-wiki, I thought of posting
my solutions.

Criticism and suggestions on the quality of the solutions I suggest are much needed and most welcome.

Thank you for reading.

// ex1-9
#include <stdio.h>

/* copy input into output, replacing
one or more blanks by a single blank */
main()
{
int c, nb=0;

Do not post tabs in Usenet; the indentation will get screwed up
when reply quotes are added, and as more levels of reply quotes
are piled on.
while ((c=getchar()) != EOF) {
/* if char is blank increase counter else reset counter.
this notation will be mentioned later in the book
(T) ? A : B; if TRUE then A else B */
(c == ' ') ? (++nb) : (nb = 0);

While this is technically okay, and even laudable in some ways, most
"blub programmers" (i.e. the majority of your C programming colleagues) will
frown on a ternary conditional having its value discarded and being used as a
statement.
//ex1-12
#include <stdio.h>

#define IN 1
#define OUT 0

// print input one word per line
main()
{
int c, state;
// start without a word
state = OUT;

This could be:

int c;
enum { OUT, IN } state = out;

Don't use preprocessor symbols without a good reason, and restrict
the scope of identifiers to where they are actually used.

If you use an enum, then values of the state variable will probably be nicely
shown as OUT and IN in your debugger, too.
while ((c = getchar()) != EOF) {
// if the char is not blank, tab, newline
if (c != ' ' && c != '\t' && c != '\n') {
// inside a word
state = IN;
putchar(c);
}
// otherwise char is blank, tab, newline, word ended
else if (state == IN) {
state = OUT;
putchar('\n');
}
}
}

Multiple comparisons of integer variables are often more effectively expressed
using switch:

switch (ch) {
case ' ': case '\t': case '\n':
if (state == IN) {
state = OUT;
putchar('\n');
}
break;
default:
state = IN;
putchar(c);
break;
}

The program has the following flaw: what if the input happens to ends in the
middle of a word? That will not happen for a well-formed text stream; every
line is newline terminated. Still, in the situation of a malformed text stream,
it might be nice to do the putchar('\n') so that the output is a well-formed
text stream.

/* after loop */
if (state == IN) /* input ended/errored within word */
putchar('\n');

In general, when you write state machines like this, they often have final
clean-up actions to be done when they terminate. This is because EOF was
treated as an out-of-band signal rather than one of the symbols from
the input stream.

For that reason, it often behooves you to include EOF as a symbol.

We can incorporate that into the state machine like this:

do{
switch ((c = getchar())) {
case EOF:
case ' ': case '\t': case '\n':
if (state == IN) {
state = OUT;
putchar('\n');
}
break;
default:
state = IN;
putchar(c);
break;
}
} while (c != EOF);
 
S

Stefan Ram

Christos Kokaliaris said:
int c, nb=0;
while ((c=getchar()) != EOF) {
/* if char is blank increase counter else reset counter.
this notation will be mentioned later in the book

When I am giving exercise in my C courses, I expect
the students to use only the parts of the language
that have been covered in the course so far when
doing exercises.
(T) ? A : B; if TRUE then A else B */
(c == ' ') ? (++nb) : (nb = 0);

Could also be written as

nb = c == ' ' ? nb + 1 : 0;

. INT_MAX can be as small as 32767, and when nb == INT_MAX
then ++nb has undefined behavior. I'd use a state, but not
a counter. You don't need the exact count.
 
J

Jorgen Grahn

It has to be »int main()«.

Or preferably int main(void) or int main(int argc, char** argv).
»main()« without
»int « is archaic. If »main« is declared this
way in in you book, then the book is too old.

Yes. It's worth noting though that he says he has the 2nd edition,
and that's (I had to look it up) the ANSI C edition.

That edition should be good enough: although it doesn't cover C99 or
C11, at least it doesn't describe the pre-ANSI language, which /is/
archaic.

/Jorgen
 
Ad

Advertisements

B

Barry Schwarz

Greetings everybody,

I am a Mathematics graduate and I need
C in order to integrate it with R and
other languages I use for statistics and
simulation. I took C classes when
undergraduate but it wasn't enough.
Recently I decided to study analytically
The "C programming Language 2nd Ed" book
and solve every single exercise.
After comparing my own code with the
already suggested in the archives
of clc-wiki, I thought of posting my
solutions.

Criticism and suggestions on the
quality of the solutions I suggest
are much needed and most welcome.

Thank you for reading.

// ex1-9
#include <stdio.h>

/* copy input into output, replacing
one or more blanks by a single blank */
main()
{
int c, nb=0;

while ((c=getchar()) != EOF) {
/* if char is blank increase counter else reset counter.
this notation will be mentioned later in the book
(T) ? A : B; if TRUE then A else B */
(c == ' ') ? (++nb) : (nb = 0);
if (c != ' ' || nb < 2) putchar(c);

The first conditional expression serves no purpose. If c is not
blank, then the previous statement set nb to 0 which is less than 2.
 
K

Keith Thompson

It has to be »int main()«. »main()« without
»int « is archaic. If »main« is declared this
way in in you book, then the book is too old.

The first edition of K&R describes the pre-ANSI version of C. In that
version, "main()" is correct and equivalent to "int main()".

In C89/C90, the version of the language described in K&R2, the "implicit
int" rule was still in place; omitting the return type of a function was
legal, but was not a particularly good idea.

C99 dropped the "implicit int" rule, making "main()" illegal
(specifically a syntax error, requiring a diagnostic from the compiler).

I just checked, and K&R2 actually uses the old "main()" form in its
examples. I'd call that a minor flaw in an otherwise very good book,
and I wouldn't reject K&R2 for that reason.

The empty parentheses are also obsolescent, and have been since the 1989
ANSI C standard. The preferred way to define the main function is:

int main(void) { /* ... */ }

I've never seen a compiler that would complain about "int main()", but
"int main(void)" is more explicit (and, I've argued, more clearly valid).
 
G

glen herrmannsfeldt

(snip)
I just checked, and K&R2 actually uses the old "main()" form in its
examples. I'd call that a minor flaw in an otherwise very good book,
and I wouldn't reject K&R2 for that reason.
The empty parentheses are also obsolescent, and have been since the 1989
ANSI C standard. The preferred way to define the main function is:
int main(void) { /* ... */ }
I've never seen a compiler that would complain about "int main()", but
"int main(void)" is more explicit (and, I've argued, more clearly valid).


Writing in both C and Java, I might be a little more sensitive to
the differences between them.

Since C's main is supposed to have arguments (usually argc and argv)
but it is usual not to put them in if I am not using them, I prefer
the () form, with the (void) form when there are never any arguments.

That is unlike Java, where the argument signatures (I think that
is what they call them) must match.

To allow for varargs, most calling conventions used with C allow
for fewer arguments. As well as I know it, strictly that is only
allowed for actual varargs functions. Different calling conventions
could be used for varargs and non-varargs, but usually aren't.

I haven't looked yet how Java does System.out.format(),
though I use it more and more in my Java programs.

-- glen
 
S

Stefan Ram

glen herrmannsfeldt said:
Since C's main is supposed to have arguments

It's not.

»It shall be defined with a return type of int and with
no parameters: int main(void) { /* ... */ } or with two
parameters«

N1570, 5.1.2.2.1p1
 
Ad

Advertisements

K

Kaz Kylheku

(snip)






Writing in both C and Java, I might be a little more sensitive to
the differences between them.

Since C's main is supposed to have arguments (usually argc and argv)
but it is usual not to put them in if I am not using them, I prefer
the () form, with the (void) form when there are never any arguments.

Then use C++ as a "better C". In C++, () means (void).

This is in part thanks to Dennis Ritchie.

You see, in the "C With Classes" language, Stroustrup introduced the (void)
hack, so that () could remain C compatible. Some users hated it, and
evidently DMR and Doug McIlroy called it an abomination:

"I abandoned the use of void as an argument type meaning "no arguments" after
Dennis Ritchie and Doug McIlroy strongly condemned it as "an abomination."
Instead, I adopted the obvious notation for taking no arguments, an empty
pair of parentheses."

["Sibling Rivalry: C and C++", 2.4, Stroustrup,
http://www.stroustrup.com/sibling_rivalry.pdf ]

See, Ritchie was very sensible; he acknowledged bad things and repented.
Once we have a C like dialect with prototypes, to hell with () meaning
"no type info"!
That is unlike Java, where the argument signatures (I think that
is what they call them) must match.

C++ later came to support, but not required (void) in order to be compatible
with ANSI C, which adopted this convention.
To allow for varargs, most calling conventions used with C allow
for fewer arguments.

varargs is historic; nobody should care about about that any more.

Aldo, the type looseness in C came first, then the varargs hack!

That is to say, I don't suspect C was designed specifically to allow
things like that ("let's not have declarations of arguments so there
can be a mismatch"); coe like that just worked.
 
K

Keith Thompson

glen herrmannsfeldt said:
(snip)






Writing in both C and Java, I might be a little more sensitive to
the differences between them.

Since C's main is supposed to have arguments (usually argc and argv)
but it is usual not to put them in if I am not using them, I prefer
the () form, with the (void) form when there are never any arguments.

The () form in a declaration says that the function takes a fixed but
unspecified number and type(s) of arguments. In a definition, it says
that the function has no parameters, but it still provides a declaration
with the above meaning.

The () form is obsolescent, both for declarations and for definitions,
and has been since 1989.

The (void) form specifies that the function has no parameters.

This program:

int main() {
main(42);
}

will typically not elicit a diagnostic from the compiler. In this program:

int main(void) {
main(42);
}

the call violates a constraint, and it must be diagnosed.

Recursive calls to main are not common, but they're permitted. The same
argument applies to parameterless functions other than main.

What advantage do you see in using "int main()" rather than
"int main(void)"? I see none at all.
That is unlike Java, where the argument signatures (I think that
is what they call them) must match.

I'd say it's a mistake to base your C coding style on Java. (I'm not
sure that's what you're doing.)
 
G

glen herrmannsfeldt

(snip, I wrote)
(snip)

"I abandoned the use of void as an argument type meaning
"no arguments" after Dennis Ritchie and Doug McIlroy strongly
condemned it as "an abomination." Instead, I adopted the obvious
notation for taking no arguments, an empty pair of parentheses."
["Sibling Rivalry: C and C++", 2.4, Stroustrup,
http://www.stroustrup.com/sibling_rivalry.pdf ]
(snip)

C++ later came to support, but not required (void) in order
to be compatible with ANSI C, which adopted this convention.

(snip, I also wrote)
varargs is historic; nobody should care about about that any more.
Aldo, the type looseness in C came first, then the varargs hack!

It is all intel's fault. All the calling conventions that I knew
before the 8086 ignored extra arguments.

The 8086 has a RET n instruction that will pop bytes off the
stack at the same time as returning. Saves one instruction
(per call) and a few cycles. It worked fine with the Pascal and
Fortran compilers that were used early in the 8086 MSDOS years.

But it only works when you are sure you know how many arguments
there are.
That is to say, I don't suspect C was designed specifically to allow
things like that ("let's not have declarations of arguments so there
can be a mismatch"); coe like that just worked.

As well as I know it, ANSI C allows a different calling convention
for functions that never have variable argument lists, maybe
even STDCALL, but no-one does that. Much better to have the caller
pop the arguments.

-- glen
 
G

glen herrmannsfeldt

(snip, I wrote)
(snip)

The () form in a declaration says that the function takes a fixed but
unspecified number and type(s) of arguments. In a definition, it says
that the function has no parameters, but it still provides a declaration
with the above meaning.
The () form is obsolescent, both for declarations and for definitions,
and has been since 1989.

But not yet removed from the standard?
The (void) form specifies that the function has no parameters.
This program:
int main() {
main(42);
}
will typically not elicit a diagnostic from the compiler. In this program:
int main(void) {
main(42);
}
the call violates a constraint, and it must be diagnosed.
Recursive calls to main are not common, but they're permitted. The same
argument applies to parameterless functions other than main.

I have seen them, but never found a use for it.
What advantage do you see in using "int main()" rather than
"int main(void)"? I see none at all.

A few less characters to type. Well, I started C in the K&R days,
though maybe not by much. About 1985 or so.
I'd say it's a mistake to base your C coding style on Java.
(I'm not sure that's what you're doing.)

I suspect my Java programs look more C-like than the other way around.

If you haven't looked at Java lately, look at System.out.format().


-- glen
 
Ad

Advertisements

C

Cal Dershowitz

This is where it stops for me, and I agree.
The () form in a declaration says that the function takes a fixed but
unspecified number and type(s) of arguments. In a definition, it says
that the function has no parameters, but it still provides a declaration
with the above meaning.

The () form is obsolescent, both for declarations and for definitions,
and has been since 1989.

The (void) form specifies that the function has no parameters.

This program:

int main() {
main(42);
}

Wouldn't the new main interpret 42 as argv[0] in this instance?
will typically not elicit a diagnostic from the compiler. In this program:

int main(void) {
main(42);
}

the call violates a constraint, and it must be diagnosed.

Recursive calls to main are not common, but they're permitted. The same
argument applies to parameterless functions other than main.

Am I right that calling main within main is universally shunned?
What advantage do you see in using "int main()" rather than
"int main(void)"? I see none at all.


I'd say it's a mistake to base your C coding style on Java. (I'm not
sure that's what you're doing.)

Glen cobbles things together using whatever tools he has. Java might be
on the opposite end of where C is with object orientation.
 
B

BartC

glen herrmannsfeldt said:
It is all intel's fault. All the calling conventions that I knew
before the 8086 ignored extra arguments.

The 8086 has a RET n instruction that will pop bytes off the
stack at the same time as returning. Saves one instruction
(per call) and a few cycles. It worked fine with the Pascal and
Fortran compilers that were used early in the 8086 MSDOS years.

But it only works when you are sure you know how many arguments
there are.


As well as I know it, ANSI C allows a different calling convention
for functions that never have variable argument lists, maybe
even STDCALL, but no-one does that. Much better to have the caller
pop the arguments.

Didn't you just say that it's better the other way because it's one less
instruction?

99% of the time, the number of arguments will be fixed. It's only for
calling functions such as the printf family that variable argument lists are
needed at all (because C doesn't have special arrangements for i/o).

And there are other ways of dealing with variable numbers of arguments than
requiring the caller to pop the stack.

(Which doesn't even work very well; try: printf("%s %s"), or printf(fmt)
where fmt is defined elsewhere, which won't give warnings.)

(It can also be a nuisance when calling a C-function from a language where
the argument-pushing, and the call, are independent:
call pushparams
call [fnaddr]
add esp,... what?)
 
R

Richard Bos

Cal Dershowitz said:
This program:

int main() {
main(42);
}

Wouldn't the new main interpret 42 as argv[0] in this instance?

What new main, and what argv[0]? There is no new main; the old main()
simply calls itself. And, clearly, that main does not even consider
anything called "argv[0]".
Am I right that calling main within main is universally shunned?

Shunned is slightly too strong. _Slightly_. You'd have to have a very
good reason to use it. It's not _as_ bad as gets(), but certainly worse
than goto. goto can be put to good use in, e.g., a state machine, and
only Lisps advocates and Pascal users would complain, but it should be
avoided unless it's necessary. A recursive call to main() is in the same
boat, except that good uses for it are even less common. gets(), by
contrast, is _never_ the right choice, and should indeed be shunned.

Richard
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Top