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

Discussion in 'C Programming' started by Christos Kokaliaris, Jun 9, 2014.

  1. 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');
    }
    }
    }
     
    Christos Kokaliaris, Jun 9, 2014
    #1
    1. Advertisements

  2. Christos Kokaliaris

    Stefan Ram Guest

    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).
    That sounds very good. When you are done with
    »The C Programming Language«, then do the same
    with »The Art Of Computer Programming«! ;-)
    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.
     
    Stefan Ram, Jun 9, 2014
    #2
    1. Advertisements

  3. 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');
    }
    }
    }
     
    Christos Kokaliaris, Jun 9, 2014
    #3
  4. Christos Kokaliaris

    Kaz Kylheku Guest

    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 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.
    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.
    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);
     
    Kaz Kylheku, Jun 9, 2014
    #4
  5. Christos Kokaliaris

    Stefan Ram Guest

    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.
    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.
     
    Stefan Ram, Jun 9, 2014
    #5
  6. Christos Kokaliaris

    Jorgen Grahn Guest

    Or preferably int main(void) or int main(int argc, char** argv).
    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
     
    Jorgen Grahn, Jun 9, 2014
    #6
  7. 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.
     
    Barry Schwarz, Jun 9, 2014
    #7
  8. Thank you for the details. They were really helpful.
     
    Christos Kokaliaris, Jun 9, 2014
    #8
  9. 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).
     
    Keith Thompson, Jun 9, 2014
    #9
  10. (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.

    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
     
    glen herrmannsfeldt, Jun 9, 2014
    #10
  11. Christos Kokaliaris

    Stefan Ram Guest

    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
     
    Stefan Ram, Jun 9, 2014
    #11
  12. Christos Kokaliaris

    Kaz Kylheku Guest

    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"!
    C++ later came to support, but not required (void) in order to be compatible
    with ANSI C, which adopted this convention.
    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.
     
    Kaz Kylheku, Jun 9, 2014
    #12
  13. 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.
    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.)
     
    Keith Thompson, Jun 9, 2014
    #13
  14. (snip, I wrote)
    (snip, I also wrote)
    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.

    -- glen
     
    glen herrmannsfeldt, Jun 10, 2014
    #14
  15. (snip, I wrote)
    But not yet removed from the standard?
    I have seen them, but never found a use for it.
    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 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
     
    glen herrmannsfeldt, Jun 10, 2014
    #15
  16. Christos Kokaliaris

    James Kuyper Guest

    Yes, as he said - it's obsolescent, not obsolete.
     
    James Kuyper, Jun 10, 2014
    #16
  17. This is where it stops for me, and I agree.
    Wouldn't the new main interpret 42 as argv[0] in this instance?
    Am I right that calling main within main is universally shunned?
    Glen cobbles things together using whatever tools he has. Java might be
    on the opposite end of where C is with object orientation.
     
    Cal Dershowitz, Jun 10, 2014
    #17
  18. Christos Kokaliaris

    BartC Guest

    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?)
     
    BartC, Jun 10, 2014
    #18
  19. Christos Kokaliaris

    Richard Bos Guest

    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]".
    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
     
    Richard Bos, Jun 10, 2014
    #19
  20. Christos Kokaliaris

    Richard Bos Guest

    That's like using Valspeak as a "better English". Like, gag me with a
    compiler.

    Richard
     
    Richard Bos, Jun 10, 2014
    #20
    1. 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 (here). After that, you can post your question and our members will help you out.