From: ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe)
Newsgroups: comp.lang.c
Subject: Re: Updated and improved "testing competence" questions
Date: 15 Dec 92 07:02:28 GMT
Organization: Comp Sci, RMIT, Melbourne, Australia

In article <1gbstkINNfcp@almaak.usc.edu>, ajayshah@almaak.usc.edu (Ajay Shah) writes:
> Questions for an exam on C (for hiring purposes)

I propose the following.  They are all of them found in a C textbook,
published this year.  The book was written by three authors, two of whom
are "chairs" of their CS departments, and they thank two other academics
for "debugging" the text and also someone from Bell Labs who has himself
published several C-related books.  All examples are SUPPOSED to be ANSI C.

A.  What is wrong with the following?  (From p7)

    #include <stdio.h>
    void main(void)
	{
	    int n;
	    scanf("%d", &n);
	    if (n < 1) {
		fprintf(stderr, "Improper value of n\n");
		exit(1);
	    }
	    printf("n = %d\n", n);
	}

    (Hint:  there is an outright error and there is also a portability
    problem.)

B.  What is wrong with the following?  (From pp 574-577)

    #include <stdio.h>
    #define MAX_CHAR 10
    main(void)
	{
	    char key[MAX_CHAR];
	    printf("Enter a key: ");
	    scanf("%s", &key);
	    printf("[%3d] =  %s0, 0, key);
	}

    (Hint:  I have accurately copied a typo.  See "F" below about _that_!
    There is another problem.)

C.  Here is a procedure for adding things to a stack (from p104).

	#define MAX_STACK_SIZE 100
	element stack[MAX_STACK_SIZE];
	int top = -1;

	void add(int *top, element item)
	    {
		if (*top >= MAX_STACK_SIZE-1) {
		    stack_full();
		    return;
		}
		stack[++*top] = item;
	    }

    Can you suggest a more idiomatic way of doing it?
    Here is a procedure that tries to use it. (from p204)

	void iter_inorder(tree_pointer node)
	    {
		int top = -1;
		tree_pointer stack[MAX_STACK_SIZE];
		for (;;) {
		    for (; node; node = node->left_child)
			add(&top, node);
		    node = delete(&top);
		    if (!node) break;
		    printf("%d", node->data);
		    node = node->right_child;
		}
	    }

    Why doesn't this work?
    Is it POSSIBLE to make this work in ANSI C without changing the
    interface of add()?
    How could you use 'struct' and 'typedef' to define a stack type
    and how could you change the interface of add() so that add()
    could work with any number of stacks (all of the same type)?

D.  This is supposed to be a declaration for the standard 'strncat'
    function.  What is wrong with it?

	char *strncat(char *dest, char *src, int n);

E.  The book says that you can use either of these two declarations:
 (1)
    typedef struct {
	    char name[10];
	    int age;
	    float salary;
	    } human_being;
 (2)
    typedef struct human_being {
	    char name[10];
	    int age;
	    float salary;
	    };
    _Are_ these two declarations equivalent?  If not, what's the
    difference?  Does either of them have a superfluous keyword?
    (This one comes from p54.  On p57 they say "The size of an object
    of a struct or union type is the amount of storage necessary to
    represent the largest component..." but that's too easy to ask.)

F.  The authors say that :All programs and algorithms in this text have
    been compiled and tested. ...  We have directly imported the compiled
    programs into the body of the book ... so as not to inject any errors."
    That's on p xii.  On p57 we find this declaration.  Why does it raise
    serious doubts about the claim on p xii?

	typedef struct list {
		char data;
		list *link ;
		} ;

    Was this legal in K&R C?  Is it legal in ANSI C?  Is it legal in C++?

G.  Here is part of a function (from p 91):

	void fail(char *pat)
	    {
		int n = strlen(pat);
		...
	    }

    What, if anything, could go wrong with that initialised declaration,
    and under what circumstances?

> 2.  Input is a pointer to an array of 10 integers.  Sort the array.
> 
> He can do it anyway he likes, and there is a spread in the
> possibilities: buggy < hand- written bubblesort < hand-written
> quicksort < using qsort(3).  This helps us seperate the men from the
> boys.

Why won't you accept insertion sort (which is simpler and faster than
bubblesort) or selection sort, or merge sort, or heap sort (there are
modern variants which are _supposed_ to do N.lgN + N.lg lg N + eps
comparisons), or bucket sort, or radix sort, or ...   For this size
of problem, I'd use insertion sort.

> 3. How do you set up .c and .h files ? What goes into the .h and what
> in the .c ?

My word, that's a good question.  I have (no kidding) come across a book
which suggests having a project-wide "types.h" and a project-wide
"consts.h".  Such books are not to be discarded lightly, they are to be
taken back to the store with a demand for a refund.

> 4. What tool will help you decipher 'char (*(*x())[])()'?

> answer: either "my brain" or "cdecl".

In practice, both answers are wrong.  The declaration is almost certainly
NOT what the author intended.  The right answer is "thumbscrews, attached
to the perpetrator until s/he confesses what s/he meant".  (:-) (:-) (:-)
In the days before Cdecl, I once hacked a copy of pcc to print out what
it thought the types were...

> 6. (trivial competence measurement)
>    Consider this code fragment:
> 
>    void abc(char *cde)
>    {
>       for (; *cde; ++cde) *cde = toupper(*cde);
>       return cde;
>    }
> 
>    a) Roughly what is he trying?  (convert a string to uppercase).
>    b) Comment on the code.
>         - it's a void and he's returning cde
>         - he is not checking if cde == NULL
What is he supposed to _do_ if cde == NULL?  That's not the only value
of cde which will make the program misbehave.

>         - For portability, you need to check isascii() and islower()
>           of *cde before calling toupper().  pre-ANSI compilers do
>           not promise that toupper() will work correctly for all inputs.

Absent any #include <ctype.h>, if one is considering pre-ANSI compilers
toupper() may not be available at all, or may call the user's function.
(In 4.2BSD toupper() was a macro with _no_ function to back it up.)

Using isascii() will IMPAIR portability to ANSI C; there ain't no such
animal as isascii() in ANSI C.  It is also unreasonable; all the machines
I use support accented letters which ANSI toupper() can handle but which
do not satisfy the isascii() test.

In ANSI C there is another problem.  toupper() is defined over the range
{EOF} U {0..UCHAR_MAX}, but char may be signed, so *cde may lie outside
this range.  The function should be

	char *upcase_str(char *cde)
	    {
		unsigned char *p = (unsigned char *)cde;
		while (*p) *p++ = toupper(*p);
	    }

>    c) what header files are needed for this to compile?
>         - toupper() is in ctype.h, but you don't need it since the
>           implicit declaration `int toupper()' is compatible with
>           the truth `int toupper(int)'.
>
This is true in ANSI C and in System V UNIX K&R; it was not true in
BSD K&R.  

> 
> 7. There is a C program which contains the line
> 
> #include "min.h"
> 
>    Now min.h EITHER contains
>         #define min(a, b) (a)<(b)?(a):(b)
>    OR
>         int min(int a, int b);
>    but you don't know which.
> 
>    Write a test program which will tell you what is in min.h
>    You are not allowed to know anything about how your program is linked;
>    you are only allowed to write this program, and see it's behaviour in
>    compilation and execution.

Use #ifdef min
By the way, #define min(a,b) (a) < (b) ? (a) : (b)
would benefit from another set of parentheses, otherwise someone could
get a nasty shock.  With the exact definition given,
	printf("0 means macro, 1 means function: %d\n", min(0,1) + 1);
will do the job.

> 8. What does this do: printf("shop"+1);
>    - prints "hop" to stdout, given you included stdio.h.

In either K&R C or ANSI C, the answer is "well, printf() could be a
static function or a macro in this file, and if it is I have no idea
what it does."  In ANSI C, if it isn't either of those, you _must_
#include <stdio.h> or give it the proper prototype some other way.
In K&R C, you could usually get away without declaring printf() or
scanf().  In fact, under V7+ UNIX on a PDP-11/60, I usually _didn't_
#include <stdio.h> if I could help it; if the first line of a file
didn't being with '#' the preprocessor wasn't run, and that saved
enough compile time for me not to use the preprocessor if I didn't
need it.
	
> 9. What is the difference between 'X' and "X"?
>    - 'X' is just the integer 88.  "X" is a pointer to a scratch space
>      containing 88 followed by 0.

What do you mean "a scratch space"?  "scratch" always implies that you
may overwrite it.  String literals may _not_ be overwritten.  If you
have "FAX" and "X" in a program, it is possible that "X" == 2+"FAX".
And surely everyone knows that A..I = C1..C9, J..R = D1..D9, S..Z = E2..E9.
So 'X' has the value 0xE7.  (Hint for non-antiquarians:  that's EBCDIC.)