K&R p 130

J

James Kuyper

Nick said:
why "usually"?

The answer to that depends upon what you mean by that question. Are you
asking why it is usually the case, or are you asking why it is "only "
usually, and not "always"?

To answer the first question, it's usual because it is very common for
function prototypes and the structure definitions that they reference to
be used in multiple different source code files; the best way to make
sure that they are defined in a consistent fashion is to put them into a
single file which is #included into the other files.

If you're asking why this isn't "always" used, the answer is that not
all prototypes and structures are used in multiple source code files. If
they aren't, you can provide them in the one source code file where they
are actually used, so there's no need for a separate header file. This
has the advantage that you can know, with a certainty, that changes made
to such structure definitions will be seen only inside that source code
file, which makes maintenance easier.
 
M

mdh

it's usual because it is very common for function prototypes and the structure definitions that they reference to be used in multiple different >source code files; the best way to make sure that they are defined in a consistent fashion is to put them into a
single file which is #included into the other files.

Does this not lead to multiple definitions of an object then and thus
an error? Or I have probably misunderstood you :)
 
J

James Kuyper

mdh said:
Does this not lead to multiple definitions of an object then and thus
an error? Or I have probably misunderstood you :)

I should have said structure declarations, not structure definitions.
Actual structure definitions do not belong in header files, for
precisely the reason you mention. However, structure types should be
declared in header files, if they are used in multiple source code files.
 
C

Chris Torek

... Let me write [this] out compressed for convenience:

int main(void)
{
struct point { int x, y; }; /* The struct tag point has a */
struct point makepoint(int, int); /* meaning all the way down */
struct point p = makepoint(1, 2); /* to here... */
}

struct point makepoint(int a, int b) /* ... but not here where it */
{ /* is also needed. */
/* stuff */
}

Outside main, "struct point" has no meaning,

Well, this is not *strictly* true, but the rest is:

OK..., this is unique only to structs, unions and enums. And if so,

(Presumably the statement here -- ending with a period -- was
meant as a question.) Yes, it is in fact unique to those three.
is this due to the complicated nature of those objects,

Not exactly. Instead, it is due to the fact that those three
things are the three ways that C allows users to define new
data-types.

The "struct" keyword does one of three things:

- refer to an existing struct type;
- declare a new, incomplete struct type; or
- define a new struct type (or -- really the same thing --
complete an incomplete one).

The union and enum keywords do the same; the only real difference
is when defining types (because a union defines a bunch of
"overlapping" members, only one of which you can use at a time,
and an enum defines a bunch of integer constants).

You get a new type defined when you use a brace-enclosed list after
the keyword and (optional, in this case) tag. You refer back to
an existing type when you use a tag you already defined. You create
a new, incomplete type when you use a tag you have not yet defined.
In all three cases, assuming that you do in fact use a tag, you
write:

struct tag_name_goes_here /* and then some more stuff here... */

and I like to say that the tag is the "true name" of the type, but
it is not, quite.

Tag names have scope, like ordinary variable names and like function
names; but tag names have no linkage. When a tag name goes out of
scope, the type associated with that tag still exists, but can no
longer be named. You cannot even use linkage to get it back,
because the names have none.

If you only ever declare and/or define struct types at file scope,
the scope issue becomes irrelevant. It matters when you declare
and/or define them at block scopes, though (and, for that matter,
at function-prototype scopes). This is what Ben's compressed
example above does. The type "struct point" is declared-and-defined
(all at once) as the first code line within main(). The type
begins to exist at that point, and is name-able at that point.
The type still exists after the closing brace, but is no longer
name-able.

The subsequent "struct point" creates a *new* (and different)
user-defined type, also named "struct point", in the same way that
if you declared a new variable named "p" outside main() it would
be a new, different "p", even though main() also has a variable
named "p". Because there is no open brace after "struct point",
though, this new type is merely *declared*, not defined. It thus
becomes an incomplete type.

Normally, you can -- and people often do -- declare incomplete
types and then complete them later. This happens every time you
make self- or mutually-referential structures. (For self-referential
structures, it happens only because the type is not complete
until the closing brace that ends the member-list.) That is:

struct name_value_list {
struct name_value_list *next;
char *name;
int value;
};

or:

/* files open in the editor are represented by lists of blocks */
struct buffer_list {
struct file *containing_file;
struct buffer_list *forw, *back;
int used; /* amount of contents[] actually in use */
char contents[SIZE];
};
/* and there can be multiple open files */
struct file {
struct file *forw, *back;
struct buffer_list *head;
... other data here as needed ...
};

There is no way to rearrange the second pair of structures so that
both of them are completed before the second one is defined. :)
So it is absolutely necessary to allow incomplete user-defined
types to exist, and in some cases, you can leave them incomplete
for an entire translation unit (provided you work only with pointers
to them, for instance -- this is a reasonable way to do "data
hiding": keeping client code from peeking into implementation
details that you would like to be able to change without notice).

The incomplete types arise as soon as the "struct" keyword (or
union or enum, for those two cases) is followed by a tag that
is not defined or declared yet. This does have one drawback: a
typo in a tag-name creates a new type, even if you did not mean
to. For instance:

struct list {
struct lsit *next;
... data members ...
};

This creates a "struct list", but its "next" member points to a
"struct lsit" instead of a "struct list", where presumably you
meant for it to point to another "struct list". When you try to
use p->next, you will get complaints, either about an incomplete
type (if you refer to p->next->field) or an incompatible type (if
you attempt to set p->next = new_list_elem, where new_list_elem is
a "struct list *"). This is, I think, one of several reasons that
some people like "typedef" aliases. (For more on this, see
<http://web.torek.net/torek/c/types2.html>.)

The same problem -- typographic errors -- can bite you with
function prototypes:

void print_list(struct lsit *); /* oops */

This declaration compiles well enough, because "struct lsit" is
created as a new, incomplete type with function-prototype scope.
The type becomes totally inaccessible as soon as the prototype
ends, though, so it is not very useful. At least one compiler
(gcc) warns if you do this. Note that the same problem hits you
even if you spell the type-name correctly, if you fail to provide
at least an incomplete declaration first:

double eval(struct tree *); /* evaluate expression tree */
struct tree *binary_op(int op, struct tree *, struct tree *);
...

The fix for this is to make sure that two word phrase "struct tree"
occurs at file scope *before* any of the parenthesized, function-prototype
scope mentions. In this case, rearranging the two function prototypes
will do the trick; or you can include an incomplete declaration:

struct tree;
double eval(struct tree *);
...

It is always OK to put in an "extra" incomplete declaration at file
scope: this is just saying "by the way, there is a user-defined
type named tree, at file scope", and if the compiler already knows
that, it shrugs its metaphorical shoulders, mutters "I knew that
already", and moves on. (But if it did not know that before, well,
it does now, and all is well.)

There are a couple more tricky bits involving tags and scope. If
you have a user-defined type at an outer scope level (such as "file
scope") and then use the same tag in an inner scope -- block or
prototype -- are you referring to the existing name, or creating
a new one? The answer is: "it depends". In most C code, people
mostly want to refer to the existing user-defined type, and this
happens entirely naturally:

struct list {
struct list *next;
...
};

void print_list(struct list *head) {
struct list *p;

for (p = head; p != NULL; p = p->next)
...
}

The "struct list" in the formal parameter ("head") and the local
variable ("p") declarations refers back to the file-scope "struct
list". If you want to override it, that also tends to happen
fairly naturally:

struct blah { char *argh; double yikes; };

void yucky_function(void) {
struct blah { int *p; long *q; } mess_o_pointers[10];
... code in here that uses "struct blah" refers to the
local one ...
}

struct blah *find_blah(char *name) {
... this code refers back to the file-scope "struct blah" ...
}

The exception occurs when you need mutually-referential structures
within a block, and are re-using a tag that you already used at
file scope. In this case, you have to use an "empty" declaration
to pave the way, much as with the "struct tree" example above. In
this case, the empty declaration pushes the outer-scope type aside
for the moment, creating a new, incomplete, inner-scope type:

void even_yuckier(void) {
struct blah; /* yet another different "struct blah" */
struct zorg { struct blah *p; ... };
struct blah { struct zorg *q; ... };
...
}

Without the empty declaration, "struct zorg"'s "struct blah *p"
member would refer to the file-scope "struct blah", not the
block-scope one.

(While I was writing this, I wondered again what happens with:

struct X;
struct X *global_px;
void f(void) {
struct X { int a; } localvar;
...
global_px = &localvar; /* error? or OK? */
...
}

Does the inner "struct X" complete the outer type, or does it create
a new type? I was never sure of the answer based on the C Standards,
but gcc, at least, creates a new inner struct, as shown by the fact
that the variable "localvar" has the wrong type, so that gcc prints
a diagnostic. If you *can* complete an outer incomplete type, this
might possibly lead to an interesting problem with typedef-names,
so gcc's result here is good. Whether it is standard-conforming,
I am not sure.)
 
R

Ron Ford

Using google groups, I see your posts frequently. I think that Ron
Ford person is a troll.
Google some of his previous articles in comp.lang.c

It doesn't take long to remember why I avoid clc. vippstar, you're an
anal-retentive tool. Please update your killfile.

Ben, I still don't see your posts, and this is the only scoring I have for
clc:
[comp.lang.c]
!delete From "Ari"
 
H

Harald van Dijk

Ben, I still don't see your posts, and this is the only scoring I have
for clc:
[comp.lang.c]
!delete From "Ari"

I don't actually know your newsreader's filter, but unless it's case
sensitive or searches for words instead of characters, you're _telling_ it
to delete Ben Bacarisse's messages.
 
D

Default User

Ron Ford wrote:

It doesn't take long to remember why I avoid clc. vippstar, you're an
anal-retentive tool. Please update your killfile.

Regardless, other NIN users do see his posts. The problem lies with you.
Ben, I still don't see your posts, and this is the only scoring I
have for clc:
[comp.lang.c]
!delete From "Ari"

Are you sure that isn't hitting "bacARIsse"?




Brian
 
R

Richard

Ron Ford said:
It doesn't take long to remember why I avoid clc. vippstar, you're an
anal-retentive tool. Please update your killfile.

He's a Heathfield suck up.
Ben, I still don't see your posts, and this is the only scoring I have for
clc:
[comp.lang.c]
!delete From "Ari"

--
 
D

David Thompson

On 19 Aug 2008 07:59:18 GMT said:
(Presumably the statement here -- ending with a period -- was
meant as a question.) Yes, it is in fact unique to those three.


Not exactly. Instead, it is due to the fact that those three
things are the three ways that C allows users to define new
data-types.

The "struct" keyword does one of three things:

- refer to an existing struct type;
- declare a new, incomplete struct type; or
- define a new struct type (or -- really the same thing --
complete an incomplete one).
Yes. #1 existing _and visible_, to be absolutely precise.
The union and enum keywords do the same; the only real difference
is when defining types (because a union defines a bunch of
"overlapping" members, only one of which you can use at a time,
and an enum defines a bunch of integer constants).
union yes, but standardly you can't forward-declare enum (6.7.2.3p2).
I suspect this is because all struct pointers must 'smell' the same,
and so must all union pointers, but enums smell like integer types,
possibly different ones, so pointers to them are allowed to differ. On
implementations where all enums 'are' int, which are many, or all
(data) pointers look the same, which is more, it can be an extension.

<snip rest; long, and good, as usual>
- formerly david.thompson1 || achar(64) || worldnet.att.net
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

K&R p.97 8
p 145 K&R 14
Struct Member Variable Problems 1
Pointer to function in K&R C 16
Struct Member Variables Problem 0
struct point not identified by gcc 39
K$R xrcise 1-13 (histogram) 4
Lexical Analysis on C++ 1

Members online

No members online now.

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,228
Latest member
MikeMichal

Latest Threads

Top