Tuesday, December 18, 2007

C Interview Questions - Part 2

2.4b: Is there a good way of simulating OOP-style inheritance, or
other OOP features, in C?

A: It's straightforward to implement simple "methods" by placing
function pointers in structures. You can make various clumsy,
brute-force attempts at inheritance using the preprocessor or by
having structures contain "base types" as initial subsets, but
it won't be perfect. There's obviously no operator overloading,
and overriding (i.e. of "methods" in "derived classes") would
have to be done by hand.

Obviously, if you need "real" OOP, you'll want to use a language
that supports it, such as C++.

==========

3.12a: What's the difference between ++i and i++?

A: If your C book doesn't explain, get a better one. Briefly:
++i adds one to the stored value of i and "returns" the new,
incremented value to the surrounding expression; i++ adds one
to i but returns the prior, unincremented value.

==========

4.15: How do I convert an int to a char *? I tried a cast, but it's
not working.

A: It depends on what you're trying to do. If you tried a cast
but it's not working, you're probably trying to convert an
integer to a string, in which case see question 13.1. If you're
trying to convert an integer to a character, see question 8.6.
If you're trying to set a pointer to point to a particular
memory address, see question 19.25.

==========

7.7c: In a call to malloc(), what does an error like "Cannot convert
`void *' to `int *'" mean?

A: It means you're using a C++ compiler instead of a C compiler.
See question 7.7.

==========

7.11: How can I dynamically allocate arrays?

A: See questions 6.14 and 6.16.

==========

11.8b: If you can't modify string literals, why aren't they defined as
being arrays of const characters?

A: One reason is that so very much code contains lines like

char *p = "Hello, world!";

which are not necessarily incorrect. These lines would suffer
the diagnostic messages, but it's really any later attempt to
modify what p points to which would be problems.

See also question 1.32.

==========

11.14b: So what could go wrong? Are there really any systems where
void main() doesn't work?

A: It has been reported that programs using void main() and
compiled using BC++ 4.5 can crash. Some compilers (including
DEC C V4.1 and gcc with certain warnings enabled) will complain
about void main().

==========

11.33b: What does it really mean for a program to be "legal" or "valid"
or "conforming"?

A: Simply stated, the Standard talks about three kinds of
conformance: conforming programs, strictly conforming programs,
and conforming implementations.

A "conforming program" is one that is accepted by a conforming
implementation.

A "strictly conforming program" is one that uses the language
exactly as specified in the Standard, and that does not depend
on any implementation-defined, unspecified, or undefined
behavior.

A "conforming implementation" is one that does everything the
Standard says it's supposed to.

References: ISO Sec. ; Rationale Sec. 1.7.

==========

12.1b: I have a simple little program that reads characters until EOF,
but how do I actually *enter* that "EOF" value from the
keyboard?

A: It turns out that the value of EOF as seen within your C program
has essentially nothing to do with the keystroke combination you
might use to signal end-of-file from the keyboard. Depending on
your operating system, you indicate end-of-file from the
keyboard using various keystroke combinations, usually either
control-D or control-Z.

==========

12.12b: Why *does* the call

char s[30];
scanf("%s", s);

work (without the &)?

A: You always need a *pointer*; you don't necessarily need an
explicit &. When you pass an array to scanf(), you do not need
the &, because arrays are always passed to functions as
pointers, whether you use & or not. See questions 6.3 and 6.4.

==========

12.26b: If fflush() won't work, what can I use to flush input?

A: It depends on what you're trying to do. If you're trying to get
rid of an unread newline or other unexpected input after calling
scanf() (see questions 12.18a-12.19), you really need to rewrite
or replace the call to scanf() (see question 12.20).
Alternatively, you can consume the rest of a partially-read line
with a simple code fragment like

while((c = getchar()) != '\n' && c != EOF)
/* discard */ ;

(You may also be able to use the curses flushinp() function.)

==========

12.27: fopen() is failing for certain pathnames.

A: See questions 19.17 and 19.17b.

==========

13.29: My compiler is complaining that printf is undefined!
How can this be?

A: Allegedly, there are C compilers for Microsoft Windows which do
not support printf(). It may be possible to convince such a
compiler that what you are writing is a "console application"
meaning that it will open a "console window" in which printf()
is supported.

==========

17.4b: I've seen function declarations that look like this:

extern int func __((int, int));

What are those extra parentheses and underscores for?

A: They're part of a trick which allows the prototype part of the
function declaration to be turned off for a pre-ANSI compiler.
Somewhere else is a conditional definition of the __ macro like
this:

#ifdef __STDC__
#define __(proto) proto
#else
#define __(proto) ()
#endif

The extra parentheses in the invocation

extern int func __((int, int));

are required so that the entire prototype list (perhaps
containing many commas) is treated as the single argument
expected by the macro.

==========

18.9b: Where can I find some good code examples to study and learn
from?

A: Here are a couple of links to explore:

ftp://garbo.uwasa.fi/pc/c-lang/00index.txt

http://www.eskimo.com/~scs/src/

(Beware, though, that there is all too much truly bletcherous
code out there, too. Don't "learn" from bad code that it's the
best anyone can do; you can do better.) See also questions
18.9, 18.13, 18.15c, and 18.16.

==========

19.9b: How can I access an I/O board directly?

A: In general, there are two ways to do this: use system-specific
functions such as "inport" and "outport" (if the device is
accessed via an "I/O port"), or use contrived pointer variables
to access "memory-mapped I/O" device locations. See question
19.25.

==========

19.10b: How can I display GIF and JPEG images?

A: It will depend on your display environment, which may already
provide these functions. Reference JPEG software is at
http://www.ijg.org/files/ .

==========

19.17b: fopen() isn't letting me open files like "$HOME/.profile" and
"~/.myrcfile".

A: Under Unix, at least, environment variables like $HOME, along
with the home-directory notation involving the ~ character, are
expanded by the shell, and there's no mechanism to perform these
expansions automatically when you call fopen().

==========

19.17c: How can I suppress the dreaded MS-DOS "Abort, Retry, Ignore?"
message?

A: Among other things, you need to intercept the DOS Critical Error
Interrupt, interrupt 24H. See the comp.os.msdos.programmer FAQ
list for more details.

==========

19.40d: What are "near" and "far" pointers?

A: These days, they're pretty much obsolete; they're definitely
system-specific. If you really need to know, see a DOS- or
Windows-specific programming reference.

==========

20.9b: How do I swap bytes?

A: V7 Unix had a swab() function, but it seems to have been
forgotten.

A problem with explicit byte-swapping code is that you have
to decide whether to call it or not; see question 20.9 above.
A better solution is to use functions (such as the BSD
networking ntohs() et al.) which convert between the known byte
order of the data and the (unknown) byte order of the machine,
and to arrange for these functions to be no-ops on those
machines which already match the desired byte order.

If you do have to write your own byte-swapping code, the two
obvious approaches are again to use pointers or unions, as in
question 20.9.

References: PCS Sec. 11 p. 179.

====================


Next, here are the significant changes.

==========

[Q1.1 How should I decide which integer type to use?]

If for some reason you need to declare something with an
*exact* size... be sure to encapsulate the choice behind
an appropriate typedef.
---
an appropriate typedef, such as those in C99's inttypes.h.

If you need to manipulate huge values, larger than the
guaranteed range of C's built-in types, see question 18.15d.

==========

[Q1.4 What should the 64-bit type be on a machine that can support it?]

A: The forthcoming revision to the C Standard (C9X) specifies type
long long as effectively being at least 64 bits, and this type
has been implemented by a number of compilers for some time.
(Others have implemented extensions such as __longlong.)
On the other hand, there's no theoretical reason why a compiler
couldn't implement type short int as 16, int as 32, and long int
as 64 bits, and some compilers do indeed choose this
arrangement.
---
A: The new C99 Standard specifies type long long as effectively
being at least 64 bits, and this type has been implemented by a
number of compilers for some time. (Others have implemented
extensions such as __longlong.) On the other hand, it's also
appropriate to implement type short int as 16, int as 32, and
long int as 64 bits, and some compilers do.

==========

[Q1.7 What's the best way to declare and define global variables...]

A: First, though there can be many "declarations" (and in many
translation units) of a single "global" (strictly speaking,
"external") variable or function, there must be exactly one
"definition". (The definition is the declaration that actually
allocates space, and provides an initialization value, if any.)
---
translation units) of a single global variable or function,
there must be exactly one "definition", where the definition is
the declaration that actually allocates space, and provides an
initialization value, if any.

==========

(Unix compilers and linkers typically use a "common model" which
allows multiple definitions, as long as at most one is
initialized; this behavior is mentioned as a "common extension"
by the ANSI Standard, no pun intended. A few very odd systems
may require an explicit initializer to distinguish a definition
from an external declaration.)
---
by the ANSI Standard, no pun intended.)

==========

[Q1.25 My compiler is complaining about an invalid redeclaration...]

A: Functions which are called without a declaration in scope
(perhaps because the first call precedes the function's
definition) are assumed to be declared as returning int (and
without any argument type information), leading to discrepancies
if the function is later declared or defined otherwise. Non-int
functions must be declared before they are called.
---
A: Functions which are called without a declaration in scope,
perhaps because the first call precedes the function's
definition, are assumed to be declared as returning int (and
without any argument type information), leading to discrepancies
if the function is later declared or defined otherwise. All
functions should be (and non-int functions must be) declared
before they are called.

==========

[Q1.30 What am I allowed to assume about the initial values...]

These rules do apply to arrays and structures (termed
"aggregates"); arrays and structures are considered "variables"
as far as initialization is concerned.

==========

[Q1.31 This code, straight out of a book, isn't compiling]

A: Perhaps you have a pre-ANSI compiler, which doesn't allow
initialization of "automatic aggregates" (i.e. non-static
local arrays, structures, and unions). (As a workaround, and
depending on how the variable a is used, you may be able to make
it global or static, or replace it with a pointer, or initialize
it by hand with strcpy() when f() is called.)
---
A: Perhaps you have an old, pre-ANSI compiler, which doesn't allow
initialization of "automatic aggregates" (i.e. non-static local
arrays, structures, or unions).

==========

[Q1.34 I finally figured out the syntax for... pointers to functions...]

An explicit declaration for the function is normally needed,
since implicit external function declaration does not happen in
this case (because the function name in the initialization is
not part of a function call).
---
A prior, explicit declaration for the function (perhaps in a
header file) is normally needed. The implicit external function
declaration that can occur when a function is called does not
help when a function name's only use is for its value.

==========

[Q2.4 How can I implement opaque (abstract) data types in C?]

A: One good way is for clients to use structure pointers (perhaps
additionally hidden behind typedefs) which point to structure
types which are not publicly defined. It's legal to declare
and use "anonymous" structure pointers (that is, pointers to
structures of incomplete type), as long as no attempt is made to
access the members -- which of course is exactly the point of an
opaque type.

==========

[Q2.6 I came across some code that declared a structure like this...]

these "chummy" structures must be used with care, since the
programmer knows more about their size than the compiler does.
(In particular, they can generally only be manipulated via
pointers.)

C9X will introduce the concept of a "flexible array member",
which will allow the size of an array to be omitted if it is
the last member in a structure, thus providing a well-defined
solution.
---
C99 introduces the concept of a "flexible array member", which
allows the size of an array to be omitted if it is the last
member in a structure, thus providing a well-defined solution.

==========

[Q2.10 How can I pass constant... structure arguments?]

A: As of this writing, C has no way of generating anonymous
structure values. You will have to use a temporary structure
variable or a little structure-building function.
---
A: Traditional C had no way of generating anonymous structure
values; you had to use a temporary structure variable or a
little structure-building function.

The C9X Standard will introduce "compound literals"; one form of
compound literal will allow structure constants. For example,
to pass a constant coordinate pair to a plotpoint() function
which expects a struct point, you will be able to call
---
C99 introduces "compound literals", one form of which provides
for structure constants. For example, to pass a constant
coordinate pair to a hypothetical plotpoint() function which
expects a struct point, you can call

Combined with "designated initializers" (another C9X feature),
it will also be possible to specify member values by name:
---
Combined with "designated initializers" (another C99 feature),
it is also possible to specify member values by name:

==========

2.12: My compiler is leaving holes in structures, which is wasting
space and preventing "binary" I/O to external data files. Can I
turn off the padding, or otherwise control the alignment of
---
space and preventing "binary" I/O to external data files. Why?
Can I turn this off, or otherwise control the alignment of

A: Your compiler may provide an extension to give you this control
(perhaps a #pragma; see question 11.20), but there is no
standard method.
---
A: Those "holes" provide "padding", which may be needed in order to
preserve the "alignment" of later fields of the structure. For
efficient access, most processors prefer (or require) that
multibyte objects (e.g. structure members of any type larger
than char) not sit at arbitrary memory addresses, but rather at
addresses which are multiples of 2 or 4 or the object size.

Your compiler may provide an extension to give you explicit
control over struct alignment (perhaps involving a #pragma; see
question 11.20), but there is no standard method.

No comments: