CMPSC
311,
Spring 2013, Final Exam, Sample questions for review
The Final Exam will be Monday, April 29,
- Section 1, 8:00 - 9:50 am, in 112 Chambers Bldg.
- Section 2, 4:40 - 6:30 pm, in 360 Willard Bldg.
- There is no conflict exam scheduled.
- If you are registered in Section 1, then take the exam at the
scheduled time for Section 1; do not expect to be able to take
the exam at the scheduled time for Section 2.
- If you are registered in Section 2, then take the exam at the
scheduled time for Section 2; do not expect to be able to take
the exam at the scheduled time for Section 1.
The exam is closed book, closed computer, closed neighbor, no cell
phones, etc. But, you can bring three 8 1/2 x 11 sheets of paper, with your name on
each, as a "cheat sheet"; turn these in with the exam.
Class time on Wednesday and Friday, Apr. 24 and 26, will be devoted
to review only. Project solutions are posted on ANGEL.
The exam will ask questions about general knowledge of C and Unix,
and will require both programming and debugging. Any material
that was covered in class, as assigned reading, as background for
the projects, or in the projects, could be on the exam. In
particular, don't neglect the programming examples in the Intro
to Unix notes, or the exercises in the notes. The "cheat
sheet" could remind you of Unix function prototypes and C syntax,
but you should be able to answer most questions without referring to
it.
The questions will be
- True/False [20 items, 1 point each]
- Multiple Choice [15 points, five questions at 3 points
each, partial credit possible]
- Programming and questions about programs [65 points,
five questions at 10-20 points each]
- write a function or a code sequence to ...
- explain something about a function or a code sequence
- It would be a real help to have done the projects and
studied the examples from class and the lecture notes.
- Expect a linked-list question that requires you to write
obviously-correct C code.
The number of points in each category might be different, but not by
much. Some 10-point questions are in two 5-point parts.
The final exam is comprehensive for the course, with emphasis on
material since the second exam.
Sample questions and practice
problems for review
Note that these are primarily for review, and are not necessarily
intended as sample exam questions. Some of the sample
questions here are harder than are actually on the exam, and some
are actual exam questions from previous years.
True/False
- A shell script is a plain text file that is interpreted
line-by-line by a command interpreter.
- Shell scripts may contain conditional commands and loop
commands that are composed from other commands.
- It would be a good idea for stderr to be an unbuffered output stream.
- When you are adding printed messages to help understand why a
program is failing with a Segmentation Fault, the newline
character '\n'
must always be used with printf(),
as in printf("we got this
far\n");
Multiple Choice
How many of these statements are correct?
* Variables
allocated in the data segment are always initialized.
* Variables
allocated in the stack segment are always initialized.
* Variables
allocated in the heap segment are always initialized.
* Variables
allocated in the text segment are always initialized.
(1) none, they are all incorrect or
nonsense
(2) one
(3) two
(4) three
Write a code sequence to open a file, read its contents, determine
whether the file contains a line of text with exactly 17 characters,
print "yes" or "no" as appropriate, and close the file.
Partial credit for pseudo-code is possible. Note that "yes" or
"no" should be printed only once. Assume the usual Unix text
file convention for end-of-line, and ASCII characters.
Write a function that will search for file names in a given
directory. The output of the function is all file names that
start with a given string. For example, the file name "abcdef" starts with the
given string "abc", so
the function prints "abcdef"
(without the quotes). If no name matches, then print
nothing. Do not print names from directory entries that are
not regular files.
Suggestion: write or use two more functions to check the strings and
to see if a file is "regular".
Here is some actual student code. Fix it.
if
(c
== '\ ')
{
if
((c = getc(stdin)) == "n")
{
newline++;
}
if
(c
== "r")
{ ret++;
if
((c
= getc(stdin)) == '\ ')
{
if
((c = getc(stdin)) == "n")
{ ret--;
retnewline++;
}
}
}
}
Explain the difference between these four statements:
int a[];
int *a;
extern int a[];
extern int *a;
In particular, draw a diagram illustrating your answer.
How does the placement of a variable's declaration in a program
affect its storage allocation?
- What are the possible places where a variable can be declared?
- Is this question properly formulated?
Which C keywords affect the way in which a variable is allocated,
and the way in which its storage can be accessed?
Why is this declaration illegal in C?
extern static int a;
What is the scope of a global variable?
The Posix regular expression data types and functions are
regex_t the compiled internal
form of a regular expression
regmatch_t the matching position of a r.e. in a
string (starting and
ending indices)
regoff_t signed integer, offset from
a string position as an index
regcomp() compiles a r.e. written as a
string into an internal form
regexec() matches the internal form of a
r.e. against a string and
reports
position
information
regerror() transforms error codes from regcomp()
or regexec() into
readable messages
regfree() frees any dynamically-allocated
storage used by the internal
form
of
a regular expression
The types regex_t and
regmatch_t are structs,
defined with a typedef.
The function prototypes [that you need for this question] are
int regcomp(regex_t
*preg, const char *pattern, int cflags);
int regexec(const
regex_t *preg, const char *string,
size_t
nmatch,
regmatch_t pmatch[], int eflags);
void regfree(regex_t
*preg);
A typical simplified usage would be something like
regex_t re;
char buffer[1024];
if (regcomp(&re,
"[A-Z][a-z]*", 0) != 0)
{ /* failed
*/ }
int status =
regexec(&re, buffer, (size_t) 0, NULL, 0);
regfree(&re);
(a) Explain why the function regfree()
is necessary, or at least useful.
(b) Explain why the functions regcomp()
and regexec() are
separate functions, and are not combined into one function.
[Hint - the answer is an issue of function design, and is largely
independent of regular expressions.]
Suppose you want to write some functions that cooperate by sharing
some data. Write a short code sequence that demonstrates a
safe way to allocate the data, and to restrict its use to these
functions.
[There might be some problems caused by sharing data in a
multi-threaded program, so you can assume a single-threaded program
for this question.]
Why is putenv() better
or worse than setenv()?
Your
answer
must consider how putenv()
and setenv() differ in their
use of memory and pointers. When would putenv() be preferred or
not? When would setenv()
be preferred or not?
Hint: Which other data, functions and later actions should you
also consider? Would a picture help?
The regular expression "^ab+c*d$"
represents the set of strings that start with 'a', followed by one or
more 'b', followed by
0 or more 'c', and end
with 'd'.
Write the body of the following function, to return 1 if the
argument string is in this set, or 0 if not.
int func(const char * s)
Explain why this loop runs slowly. Rewrite the loop so that it
will run faster.
char *buf =
pointer_to_a_valid_character_string;
for (int i = 0; i
< strlen(buf); i++)
{ /*
loop body */ }
What specific assumptions did you make about the loop body?
Explain why this code sequence has a serious bug. Its intent
is to construct the character string b from the "tail" of character string a. Rewrite the code
sequence so that it will run correctly.
char
a[large_number]; // initialized to a valid character string
char
b[large_number];
for (int i = 8; i
< strlen(a); i++)
{
b[i-8] = a[i]; }
What is wrong with this code sequence, which prints an error
message?
[The parts marked ...
indicate code that is correct, so that's not where the bugs are.]
...
if ( ... )
{ printf("%s: failed, %s\n", strerror(errno)); exit(0); }
...
This multiple-choice question is a lot harder than it looks.
To discover whether or not the
function getc() is actually a macro,
(1)
read the Posix Standard specification for getc().
(2)
read the man page for getc().
(3)
read the include file <stdlib.h>.
(4)
write a program that prints &getc (since macros don't have
addresses).
(3) is not correct, because
you need to look at <stdio.h>
#include
<stdio.h>
int getc(FILE *stream);
The C Standard
"The getc function is
equivalent to fgetc, except that if it is implemented as a
macro, ...". The
standard requires fgetc() to be a function.
CP:AMA (p. 567)
"getc is usually implemented
as a macro (as well as a function), while fgetc is
only implemented as a
function. Since getc is normally available in macro form,
it tends to be faster."
[there is more info as well]
C:ARM (p. 375)
"The function getc is
identical to fgetc except that getc is usually implemented
as a macro for efficiency."
The Posix Standard [an old
edition]
"The functionality described
on this reference page is aligned with the ISO C
standard. Any conflict
between the requirements described here and the ISO C
standard is
unintentional. This volume of IEEE Std 1003.1-2001 defers to
the
ISO C standard."
"The getc() function shall be
equivalent to fgetc(), except that if it is
implemented as a macro it may
evaluate stream more than once, so the argument
should never be an expression
with side effects."
APUE (p. 140)
"The difference between the
first two functions is that getc can be implemented
as a macro, whereas fgetc
cannot be implemented as a macro."
The Solaris man page
"The getc() function is
functionally identical to fgetc(), except that it is
implemented as a macro."
The Linux man page
"getc() is equivalent to
fgetc() except that it may be implemented as a macro
... ."
The Mac OS X man page
"The getc() function acts
essentially identically to fgetc(), but is a macro
that expands in-line."
Test program
#include
<stdio.h>
int main(void)
{
#if
defined(getc)
printf("getc is a macro\n"); // [1]
#endif
printf("%p\n",
&getc);
//
[2]
return 0;
}
C89, 32-bit
Solaris
Linux
Mac OS X
[1]
only
macro
macro
(nothing)
[2]
only
address
address address
[1] and
[2]
both
both
address
C99, 32-bit
Solaris
Linux
Mac OS X
[1]
only
(nothing)
macro
(nothing)
[2]
only
address
address address
[1] and
[2]
address
both
address
Can you explain why outputs
[1] and [2] could both occur?
Solaris /usr/include/stdio.h
--> /usr/include/iso/stdio_iso.h
Depending on how the program
is compiled, we could have
extern int getc(FILE
*);
or
#define getc(p)
(--(p)->_cnt < 0 ? __filbuf(p) : (int)*(p)->_ptr++)
Can you explain how the macro
implementation works?