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. Christos Kokaliaris

    Joe Pfeiffer Guest

    What "new" main? The only main() in the program is the one right there,
    which doesn't have an arrgv parameter.
    Pretty close (if I were to say "yes", I'm sure somebody would come up
    with some obscure example where it might be marginally useful).
    Joe Pfeiffer, Jun 10, 2014
    1. Advertisements

  2. (Your use of the word "never" could imply that some functions might
    *sometimes* have variable argument lists. A given function either
    does or does not have a variable argument list, depending on whether
    it's defined with ", ...".)

    Yes, the standard permits variadic and non-variadic functions to
    have different calling conventions. It doesn't discuss calling
    conventions, but it expresses this by saying that a call to a
    variadic function without a visible and correct prototype has
    undefined behavior. And STDCALL is not mentioned in the standard.

    The ", ..." syntax for variadic functions was introduced by the
    1989 ANSI standard, and many calling conventions predate that, so
    most calling conventions happen to have the property that calling
    a variadic function with no visible prototype will happen to work.
    A newly defined calling convention could easily break that, but
    designers will likely try to avoid breaking existing code -- even
    if that code's behavior is undefined
    Keith Thompson, Jun 10, 2014
    1. Advertisements

  3. Correct, but notice is given that it could be removed from a future
    version of the standard.

    Are you referring to recursive calls to main?

    The argument is more general than that. If you use empty parentheses
    for parameterless functions other than main, then the issue of recursive
    calls to main doesn't matter.

    Using () rather than (void) on a *declaration* means that the compiler
    needn't (and typically won't) diagnose calls with one or more arguments.
    Using () on a *definition* is mostly harmless -- unless calls rely on
    the declaration provided by the definition. I suppose you could
    consistently use () on definitions on (void) on declarations (while
    being very careful to *always* provide a separate declaration), but it's
    a lot easier to be consistent.
    I consider that to be no advantage at all. In 1985, the void keyword
    didn't exist. It's time to start using it.
    Keith Thompson, Jun 10, 2014
  4. Christos Kokaliaris

    Stefan Ram Guest

    The word you two are looking for is »incarnation (in computer science)«.
    While there is one definition of each function, each call of a function
    creates a /new/ so called »incarnation« of the function.
    It's alright in C, but explicitly forbidden in C++. (One more reason
    not to claim that any valid C program is a valid C++ program.)
    Stefan Ram, Jun 10, 2014
  5. As far as the C standard is concerned, the behavior is undefined.

    Depending on the calling convention, main(42) *might* do something
    equivalent to setting argc (probably not argv[0]) to 42, and probably
    putting garbage into the argv pointer -- but since main's definition
    doesn't declare argc, it can't refer to it anyway. There is no
    argc or argv in this case.

    Keith Thompson, Jun 10, 2014
  6. Christos Kokaliaris

    Phil Carmody Guest

    It goes down fairly well in the IOCCC.
    Lisp had GOTO before C's parents had even met each other.

    Phil Carmody, Jun 10, 2014
  7. Christos Kokaliaris

    Stefan Ram Guest

    Newsgroups: comp.lang.c,comp.lang.lisp
    Followup-To: comp.lang.lisp

    Maybe you think of the GO function of LISP 1.5?

    It is apparently quite limited:

    It can only appear on the top level of a prog or
    within a conditional expression on the top level.

    It can only jump to a location symbol of this prog,
    not another one on a higher or lower level.

    Newsgroups: comp.lang.c,comp.lang.lisp
    Followup-To: comp.lang.lisp
    Stefan Ram, Jun 10, 2014
  8. Can you explain what you mean by the second sentence above?
    If it says that the function has no parameters, doesn't it constitute a
    declaration that says the same thing? If there exists something that says
    a function has no parameters but that is not a declaration, what is it?

    The only relevant part I could find in the standard was N1570:
    "An empty list in a function declarator that is part of a definition
    of that function specifies that the function has no parameters", but
    it doesn't seem to imply that it still provides a declaration that
    allows anything but no parameters.
    Seungbeom Kim, Jun 11, 2014
  9. Christos Kokaliaris

    Tim Rentsch Guest

    A declaration like this

    int f();

    declares a function whose type holds no information about either
    the number or types of any parameters (fine point: except that
    we know the function cannot be variadic, since declarations of
    variadic functions always need a prototype).

    A function definition like this

    int f(){ ... }

    defines a function that /in fact/ takes no parameters, but whose
    declarator still specifies a type that holds no information about
    either the number of types of any parameters.

    It may seem strange that even though the function definition
    "knows" that the function takes no parameters, it still is the
    case that what is declared is a function /whose type/ holds no
    information about either the number or types of any parameters,
    as clearly the information is available. But that is in fact
    what the C standard prescribes.
    Tim Rentsch, Jun 11, 2014
  10. Christos Kokaliaris

    Tim Rentsch Guest

    I think it's worth pointing out that this language feature (along
    with several others) is listed as obsolescent but not currently
    listed as deprecated. Being listed as obsolescent means a feature
    may be considered for withdrawal in a future revision, but IIUC
    normally first goes through a revision cycle (or possibly more
    than one, that may depend) where it is not yet removed but just
    marked deprecated. In practical terms none of the half dozen
    or so language features listed as obsolescent is going to go
    away any time soon, and pretty likely not ever.
    Tim Rentsch, Jun 11, 2014
  11. Strange as it seems, the empty parentheses in

    void foo() { /* ... */ }

    specify that foo has no parameters, but they don't specify that it takes
    no arguments. (This strangeness is easily avoided by using prototypes

    N1570 6.9.1p7:

    The declarator in a function definition specifies the name of the
    function being defined and the identifiers of its parameters. If the
    declarator includes a parameter type list, the list also specifies
    the types of all the parameters; such a declarator also serves as a
    function prototype for later calls to the same function in the same
    translation unit. If the declarator includes an identifier list,
    the types of the parameters shall be declared in a following
    declaration list.

    The second sentence says that a definition that includes a parameter
    type list, such as:

    void foo(void) { ... }

    provides a prototype, equivalent to:

    void foo(void);

    which means that, if that definition is visible, any call that passes
    one or more arguments is a constraint violation. Without a parameter
    type list, no such prototype is provided, and an incorrect call has
    undefined behavior (and needn't be diagnosed).
    Keith Thompson, Jun 11, 2014
  12. Christos Kokaliaris

    Kaz Kylheku Guest

    What? No.

    It defines a function entity with no parameters, which consequently takes no

    But the name foo is not *declared* as such: not to the scope within the
    braces, nor the subsequent portion of the file scope.

    These regions of the program are only informed about the existence of
    the name foo which refers to a function.
    Kaz Kylheku, Jun 11, 2014
  13. Christos Kokaliaris

    Richard Bos Guest

    Yeah, but don't tell a Lisp advocate that. Advocate as opposed to normal
    user - you know the kind, claims that there hasn't been a good
    programming language since Lisp (even Scheme is a bit dodgy), you can't
    do anything in C that you can't do better in Lisp, you can't do tail
    recursion in an assembler language like C (don't need to, he should say,
    and anyway it's wrong), and anyway GOTO is dirty. Why would you accuse
    Lisp of having GOTO? Lisp is the only structured programming language in
    the world! It's dirty hacks like C which use GOTO!!!!!1!

    Yah. That kind. Don't cross-post...

    Richard Bos, Jun 11, 2014
  14. Christos Kokaliaris

    Richard Bos Guest

    Well. It's _allowed_ in C. It's not exactly popular. I wouldn't shout at
    someone who finds a serious use for it as I would at someone using
    gets(), but I would blink a bit.

    Richard Bos, Jun 11, 2014
  15. Christos Kokaliaris

    Kaz Kylheku Guest

    Every decent Common Lisp advocate I know (including myself) touts
    TAGBODY/PROG and GO as useful features, and in fact the best representation for
    certain problems, and a necessary target target language for making certain
    kinds of macros as efficient as possible.

    Implementations of the ANSI CL LOOP macro usually make use of GO.
    Ah, that sounds more like a Scheme advocate than a Lisp advocate.
    Kaz Kylheku, Jun 11, 2014
  16. Christos Kokaliaris

    Phil Carmody Guest

    I think a lot of the funcy language proponents are cut from
    the same mould, I've certainly seen some of the above in my
    time. At least one of those statements is one I have said
    myself, and would again. Then again, I only learnt that lisps
    had gotos a decades after I'd last programmed in the language,
    having learnt it ('t', a scheme dialect) as part of my
    mathematics degree (before I'd learnt C), where predictably
    we focussed on the "purer" aspects of language. I had a rose
    tinted view of the language family, and was shocked by what
    I later learnt.

    Phil Carmody, Jun 11, 2014
  17. Christos Kokaliaris

    Tim Rentsch Guest

    The problem with calling main recursively is not that it's always
    wrong but almost never right. There are cases where a recursive
    call to main is (IMO) reasonable and appropriate, but these are
    rare, so for most developers it's natural to view a recursive
    calling of main with suspicion.
    Tim Rentsch, Jun 12, 2014
  18. Christos Kokaliaris

    Tim Rentsch Guest

    I recommend putting all comments before (or after) a function,
    and none in the function body itself. Also, the way the comments
    are indented here is rather jarring, but maybe that's an artifact
    of how the posting was done.

    I agree with other comments given that the setting of 'nb' is
    better done with an assignment of a conditional expression.

    In the loop, the logic for deciding whether to print is just a
    little obscure. It would be good to clarify it.

    Someone gave a comment that incrementing the counter might lead
    to overflow if there were lots of consecutive spaces. This can
    be addressed by having the increment value depend on the value
    of the counter.

    Putting all these remarks together, this might be written as

    /* copy input into output, replacing one or more blanks
    * by a single blank

    /* We use a variable 'nb' to count how many blanks were last
    * seen. The counter is limited to the value 2 to prevent
    * overflow. The value of 'nb' is set using a conditional
    * expression, which is described later in the book. (etc)

    int c, nb = 0;

    while( (c = getchar()) != EOF ){
    nb = c == ' ' ? nb + (nb<2) : 0;
    if( nb == 0 ) putchar( c );
    if( nb == 1 ) putchar( ' ' );

    return 0;
    What's interesting is that this problem is almost the same as
    the first problem, just with a difference of what constitutes
    a "blank". Compare the solution shown below to the last one -

    int c, nb = 2;

    while( (c = getchar()) != EOF ){
    nb = c == ' ' || c == '\t' || c == '\n' ? nb + (nb<2) : 0;
    if( nb == 0 ) putchar( c );
    if( nb == 1 ) putchar( '\n' );

    if( nb == 0 ) putchar( '\n' );

    return 0;

    To me this way of writing the program is easier to understand
    than using the 'state' variable approach.
    Tim Rentsch, Jun 12, 2014
  19. Christos Kokaliaris

    Richard Bos Guest

    I disagree with this. Comments before the function should document
    _what_ the function does (and preferably what its inputs and output
    are), while comments inside the function should, where necessary,
    clarify _how_ it does this.
    That I agree with.

    Richard Bos, Jun 12, 2014
  20. Christos Kokaliaris

    Tim Rentsch Guest

    I agree that differentiating /what/ comments and /how/ comments
    is a good idea. That doesn't mean they should be spatially
    separated necessarily, only that the two kinds be clearly
    distinguished. Putting /what/ comments first and /how/ comments
    afterwards is obviously one way of doing that.

    Comments (of either kind) inside function bodies, on the other
    hand, is a bad idea, a bad habit acquired at an early age where
    it is used in examples as a teaching aid. The problem is that
    although it might be helpful in teaching how to program, it is
    not a good fit for doing actual development. Psychological
    experiments in dual-mode cognition (such as code and comments)
    show that people prefer to work exclusively in a single mode (ie,
    the code mode in this case) once they have reached a certain
    level of understanding with how things work. (Unfortunately I
    have lost track of where the paper is with a reference for those
    results.) Putting comments inside function bodies actually slows
    down developers who know what they are doing.

    A hybrid scheme I have sometimes used in the past puts comments
    inside function bodies only to provide markers to footnotes
    given after the function body. For example, a function might
    be written like this:

    int c, n = 0;

    while((c=getchar()) != EOF){
    n = c == ' ' ? n+(n<2) : 0; // 1
    if(n<2) putchar( c ); // 2

    return 0;
    /* Notes:
    * [1] The variable 'n' holds a count of how many spaces
    * have last been seen, limited to 2.
    * [2] If 'n' is 0, the last character was a non-space and
    * should be printed. If 'n' is 1, the last character
    * was the first space in this sequence and should be
    * printed. If 'n' is greater than 1, that indicates
    * a space after the first in a sequence, which should
    * not be printed.

    In my experience this approach is much better, on those occasions
    when comments are called for, than putting comments directly
    inside the body of a function.
    Tim Rentsch, Jun 13, 2014
    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.