Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

typedef and #define and structures

7 views
Skip to first unread message

maxw_cc

unread,
Dec 1, 2002, 5:52:14 PM12/1/02
to

Hi all,

1) How do I know how a typedef is being expanded to? In the case of
using #define is easy because of the -E flag. But since typedef is
evaluated at compile time, not preprocessing time, how would I know?

For example would it be the same to have the following??

typedef struct {
char *name;
int age;
} Person;

to do:

#define Person struct { char *name; int age; }


2) Another question what is the advantage of including the structure tag
in a typedef?

typedef struct person_typ {
char *name;
int age;
} Person;

why not only?

typedef struct {
char *name;
int age;
} Person;


Thanks a lot to all in advance...

Max

--
Posted via http://dbforums.com

Mark McIntyre

unread,
Dec 1, 2002, 7:33:59 PM12/1/02
to
On Sun, 01 Dec 2002 22:52:14 +0000, in comp.lang.c , maxw_cc
<mem...@dbforums.com> wrote:

>
>Hi all,
>
>1) How do I know how a typedef is being expanded to?

Typedefs are not expanded, they're aliases for another name. You
determine what that is by reading the code.

>For example would it be the same to have the following??
>
>typedef struct {
>char *name;
>int age;
>} Person;

this creates an alias Person for the unnamed struct.

>#define Person struct { char *name; int age; }

this causes the text "Person" to be replaced wherever it occurs.

The two are totally different. Consider a simple example
typedef struct foo {int x; } Foo;
Foo bar; // this creates an object of type struct foo.
Foo baz; // so does this

compared to
#define Foo struct foo{int x;}
Foo bar;
Foo baz; // struct redfinition, error

>2) Another question what is the advantage of including the structure tag
> in a typedef?
>
>typedef struct person_typ {
>char *name;
>int age;
>} Person;

person_typ is the name of the struct type.

>why not only?
>
>typedef struct {

If you do this, then you can only ever refer to type Person.
--
Mark McIntyre
CLC FAQ <http://www.eskimo.com/~scs/C-faq/top.html>
CLC readme: <http://www.angelfire.com/ms3/bchambless0/welcome_to_clc.html>

Chris Torek

unread,
Dec 1, 2002, 9:30:37 PM12/1/02
to
In article <2111927.1...@dbforums.com>

maxw_cc <mem...@dbforums.com> writes:
>1) How do I know how a typedef is being expanded to?

Typedefs are not "being expanded" at all. A typedef is an alias for
some other existing type. If you mean "I am handed a typedef and
want to know what type it is an alias for", the answer is: "look in
the documentation or the source to find out", because that is the
only way.

>For example ...


>
>typedef struct {
>char *name;
>int age;
>} Person;

This declaration consists of two parts. The first part is:

struct {
char *name;
int age;
}

If you were to insert a valid structure tag such as "s" before the
"{" character, this would create the structure named "struct s",
or refer to an existing "struct s" if one is in scope (except for
the "degenerate case", where you want to override an outer-scope
"struct s", but presumably that would not apply, and I will ignore
it for the rest of this article). If there were an existing
"struct s" at the current scope, and you tried to define a new one,
with the "{", you would get a diagnostic. If there were an
existing one at some outer scope, you would define a new one
at the current (inner) scope, hiding the outer one, in the same
way that variables in inner scopes can hide those in outer scopes.[%]

Since there is no tag here, this creates a new structure, but it
has no name and can never be referred to again. Fortunately, the
structure thus created is still accessible up until the semicolon
that will end the sequence, and we have not gotten there yet.

Now, outside of this structure-creation construct, you have added
the keyword "typedef" and an alias name, "Person". If you were to
use the structure tag method:

struct s { char *name; int age; };

then you could see how the later typedef refers back to the struct
just created:

typedef /* set up to make aliases for an existing type */
struct s /* here is the existing type */
Person /* here is the first alias */
; /* and here the list ends, so we create only one alias */

In this case, although there is no tag -- which makes it hard for
me to talk about the "true name" of the structure, as there is no
externally-visible name -- the typedef still manages to latch on
to this "true name", and give it an alias:

typedef /* set up to make aliases for an existing type */
struct { ... } /* create a new type; whew, at } we got it created */
Person /* here is the first alias */
; /* and here the list ends */

Even though there is no "true name" like "struct s" for this new
type, the alias "Person" is still just an alias for the true but
un-name-able name. It is the sequence "struct {" that created the
new type, NOT the typedef keyword.

Despite all sanity, the "typedef" keyword NEVER defines a new type.
It only makes aliases for existing types. It is the "struct"
keyword that defines new types.

>[could I do something like this instead:]


>#define Person struct { char *name; int age; }

If you read through the above enough times to internalize it fully.
the answer becomes obvious: no, this is very different. The #define
expands in place each time, so each time you write, for instance:

Person x;
Person y;

it expands to:

struct { ... } x;
struct { ... } y;

Since each struct has no tag, this *always* creates a new type,
every time. The variables "x" and "y" therefore have different,
incompatible types.

>2) Another question what is the advantage of including the structure tag
> in a typedef?

The tag is the "true name". You can use it before you define the
structure, to refer back to the structure. If you intend to define
structures that point to instances of themselves, or to instances
of other structures that point back to instances of themselves,
you *must* use at least one tag.

Consider, for instance, the simple ordinary linked-list, perhaps
a list of "int"s. Each list entry must point to the next entry
in the list. If we use structure tags, this is easy:

struct list_of_ints {
int value;
struct list_of_ints *next;
};

The sequence "struct list_of_ints" tells the C compiler: "Please
look for some existing type named struct list_of_ints, and if there
is none, create one, then in any case get a handle on the one found
or created." Of course, there is no existing type "struct list_of_ints"
yet, so the first line creates a new type and begins to define it
at the "{". The second line puts one int in it, and the third line
looks for "struct list_of_ints" and finds it -- as a so-called
"incomplete type", because we are still in the process of defining
it -- and thus makes "next" have type "pointer to struct list_of_ints".
The "}" completes the type and the ";" ends the sequence, defining
no variables of this new type.

Compare that to what happens if you omit the tag:

struct {
int value;
struct /* now what? */ *next;
};

What can you put in the place of the comment to refer to the type
being created? It has no "true name", so there is no way to refer
back to it. "Well," some answer, "I'll just use a typedef" -- but
the typedef only creates aliases for some existing type. We are
still trying to define it. You could write:

typedef struct <the-one-we-will-define-next> my_alias;

but this is not valid syntax, and if we replace the <angle-bracket>ed
part with a valid name, that turns out to be a tag name! The next
thing people try is "typedef struct { ...", but once you get to
the part where you want to define the member "next", you need to
use the typedef alias-name that you have not yet written, and cannot
write until after the "}" that finishes the structure definition.

You *can* do *this* though: first, write:

typedef struct list_of_ints my_alias;

This combines a request for "struct list_of_ints" -- making that
type spring into being as an incomplete type -- with the typedef,
making "my_alias" an alias for that same type. Having now created
both the incomplete type and the alias, you can proceed to complete
the type, and use the alias in the process:

struct list_of_ints {
int value;
my_alias *next;
};

Or, if you prefer, you can define the structure first, using tags
as usual, then add the "typedef struct list_of_ints my_alias;"
line. You can even combine the two by writing "typedef struct
list_of_ints {" as the first line -- but again, as always, it is
the "struct" keyword that creates the new type. The "typedef"
keyword is horribly misnamed: it *seems* to have something to do
with defining the new type, but it does nothing of the sort.

My personal preference is to avoid "typedef"s as much as is reasonable
(and for a beginner, this means "as much as possible"). All
structures should be given a tag, after which "struct T" really
means "type T". Since "struct T" then *means* "refer back to type
T", creating aliases -- like "T" instead of "struct T" -- via the
typedef keyword is unnecessary. All it does is save typing the
"struct" keyword. (Of course, if you are a slow typist, this may
actually be valuable.)

[% For an example of variable shadowing, consider:

double var;
void fn(void) {
int var = 3;
...
}

The "var" inside function fn() is a different variable from the
"var" outside fn(). Structure name shadowing works the same way:

struct s { double val; };
void fn(void) {
struct s { int val; };
...
}

The "struct s" inside fn() is a different structure type from
the "struct s" outside fn(). In general, it is a bad idea to
do this kind of name shadowing, for either variables or user-defined
"struct" types, as it tends to confuse people.]
--
In-Real-Life: Chris Torek, Wind River Systems (BSD engineering)
Salt Lake City, UT, USA Domain: to...@bsdi.com
http://67.40.109.61/torek/ (for the moment)
(you probably cannot email me -- spam has effectively killed email)

maxw_cc

unread,
Dec 1, 2002, 10:36:18 PM12/1/02
to

Thank you Mark, for your help... I appreciate it.

But I still encounter problems when trying to understand complex
typedefs:

I'll give a couple of examples:

1) K&R give the following example:

typedef int (*PFI) (char *, char *);

and then utilize it in

PFI strcmp, numcmp;

In this case how would I spell out explicitly (expand) this declaration
so that it would make sense?

2) An example I took from a posting to comp.lang.C from Dan Pop:

"To see what I mean, have a look at the prototype of the signal
function,
as defined by the C standard:

void (*signal(int, void (*)(int)))(int);

You can easily spend minutes staring at it without figuring out the
meaning of this gibberish. A single typedef can do wonders here:

typedef void (*handler_t)(int);
handler_t signal(int, handler_t);"


This typedef certainly does wonders... unfortunately I do not yet
understand it...

In this case "a pointer to a function returning void and taking an int
argument" is declared to be a type and aliased to handler_t....???

Well any help in unscrambling this would be certainly very
appreciated...

Thanks to all in advance that take the time to help me....

maxw_cc

unread,
Dec 1, 2002, 11:38:18 PM12/1/02
to

My biggest thanks to Chris Torek who gave me an excellent explanation...

I do really appreciate it Chris....

If it wouldn't too much to ask could you also help in unscrambling the
complex typedefs i've posted in my previous post-reply. I mean the:

1) K&R give the following example:

typedef int (*PFI) (char *, char *);

and then utilize it in

PFI strcmp, numcmp;

In this case how would I spell out explicitly (expand) this declaration
so that it would make sense?


2) An example I took from a posting to comp.lang.C from Dan Pop:

"To see what I mean, have a look at the prototype of the signal
function,
as defined by the C standard:

void (*signal(int, void (*)(int)))(int);

You can easily spend minutes staring at it without figuring out the
meaning of this gibberish. A single typedef can do wonders here:

typedef void (*handler_t)(int);
handler_t signal(int, handler_t);"


This typedef certainly does wonders... unfortunately I do not yet
understand it...

In this case "a pointer to a function returning void and taking an int
argument" is declared to be a type and aliased to handler_t....???


Again thanks to all who take part of your time and wisdom to
help me...

Ben Pfaff

unread,
Dec 2, 2002, 2:30:59 AM12/2/02
to
maxw_cc <mem...@dbforums.com> writes:

> 1) K&R give the following example:
>
> typedef int (*PFI) (char *, char *);
>
> and then utilize it in
>
> PFI strcmp, numcmp;
>
> In this case how would I spell out explicitly (expand) this declaration
> so that it would make sense?

int (*strcmp) (char *, char *), (*numcmp) (char *, char);

> typedef void (*handler_t)(int);
> handler_t signal(int, handler_t);"
>
>
> This typedef certainly does wonders... unfortunately I do not yet
> understand it...

The typedef defines handler_t as a pointer to function taking an
int and returning void. This is the same form as the typedef for
PFI above. Just substitute this type into signal() where
handler_t is written: signal is a function taking int and
handler_t and returning handler_t. Is that good enough? I can
go into further detail if you explain the sticking point.
--
"It would be a much better example of undefined behavior
if the behavior were undefined."
--Michael Rubenstein

Kevin Easton

unread,
Dec 2, 2002, 3:10:43 AM12/2/02
to
maxw_cc <mem...@dbforums.com> wrote:
>
> Thank you Mark, for your help... I appreciate it.
>
> But I still encounter problems when trying to understand complex
> typedefs:
>
> I'll give a couple of examples:
>
> 1) K&R give the following example:
>
> typedef int (*PFI) (char *, char *);
>
> and then utilize it in
>
> PFI strcmp, numcmp;
>
> In this case how would I spell out explicitly (expand) this declaration
> so that it would make sense?

You do it the same way you do any complex declaration. They are
basically two solutions. The first is to read the declaration
"inside-out": start at the name, and apply the
things-that-look-like-operators one at a time, in the usual order of
precedence.

As an example, we'll decode your typedef int (*PFI) (char *, char *);

The name is PFI, so we start there. "PFI is a ...".

The next quasi-operator is the unary *, so we apply that. "PFI is a
pointer to...".

The next is the function-call operator. "PFI is a pointer to function
taking two char * parameters...".

And we finish up with a type (in this case, int). "PFI is a pointer to
a function taking two char * parameters and returning int".

Or in other words, if we start with PFI, apply a unary *, then apply a
(char *, char *), we end up with an int.

The second way to decode complex functions is to get a copy of the
program "cdecl" and ask it for the answer:

cdecl> explain int (*PFI) (char *, char *);
declare PFI as pointer to function (pointer to char, pointer to char)
returning int

(my copy of cdecl doesn't seem to understand typedefs, but just replace
"declare <name> as" with "define <name> as an alias for".



> 2) An example I took from a posting to comp.lang.C from Dan Pop:
>
> "To see what I mean, have a look at the prototype of the signal
> function,
> as defined by the C standard:
>
> void (*signal(int, void (*)(int)))(int);
>
> You can easily spend minutes staring at it without figuring out the
> meaning of this gibberish. A single typedef can do wonders here:
>
> typedef void (*handler_t)(int);
> handler_t signal(int, handler_t);"
>
>
> This typedef certainly does wonders... unfortunately I do not yet
> understand it...
>
> In this case "a pointer to a function returning void and taking an int
> argument" is declared to be a type and aliased to handler_t....???

Well, it's not declared to be a type, it just is a type. But yes,
handler_t is being defined as an alias for this type.

- Kevin.

Barry Schwarz

unread,
Dec 2, 2002, 5:35:51 PM12/2/02
to
On Mon, 02 Dec 2002 04:38:18 +0000, maxw_cc <mem...@dbforums.com>
wrote:

>
>My biggest thanks to Chris Torek who gave me an excellent explanation...
>
>I do really appreciate it Chris....
>
>If it wouldn't too much to ask could you also help in unscrambling the
>complex typedefs i've posted in my previous post-reply. I mean the:
>
>1) K&R give the following example:
>
>typedef int (*PFI) (char *, char *);
>
>and then utilize it in
>
>PFI strcmp, numcmp;

Look in section 5.12 of K&R. They discuss how to interpret complex
declarations.


<<Remove the del for email>>

Chris Torek

unread,
Dec 6, 2002, 8:45:03 AM12/6/02
to
In article <2112579.1...@dbforums.com>

maxw_cc <mem...@dbforums.com> writes:
>If it wouldn't too much to ask could you also help in unscrambling the
>complex typedefs i've posted in my previous post-reply. I mean the:
>
>1) K&R give the following example:
>
>typedef int (*PFI) (char *, char *);
>
>and then utilize it in
>
>PFI strcmp, numcmp;
>
>In this case how would I spell out explicitly (expand) this declaration
>so that it would make sense?

This gets into the syntax for typedef (which, as we should all
now know, :-) does not actually define new types, but just make
aliases for existing types). The syntax borrows from ordinary
variable and function declarations, in part because that was an
easy way to add it to the C compilers available back in the 1970s.
They already knew how to do things like "register int i" to put
variable i in a machine register, so it was simple enough to add
"typedef int i" to put the name "i" in a type-alias table.

The syntax for typedef, then, is to start by writing things like:

/*typedef*/ int a, *b, c[3];

which declares three variables:

declare a as int
declare b as pointer to int
declare c as array 3 of int

Then you rudely shove the keyword "typedef" in front (by removing
the comment characters above, for instance) and it changes each
entry from "declare <variable> as <type>" into "alias <name> as
<type>":

alias a as int
alias b as pointer to int
alias c as array 3 of int

When the typedef sequence has ended (at the semicolon), all of the
names declared in it are available for use as type-aliases, so now:

b p;
c arr;

means "declare p as pointer to int" and "declare arr as array 3 of
int". Note that we can once again shove the typedef keyword in
front of any such declaration:

typedef b p;

which changes this to "alias p as pointer to int".

Thus, to see what PFI means after:

typedef int (*PFI)(char *, char *);

you might begin by *removing* the typedef keyword and seeing
what sort of variable is thus declared:

int (*PFI)(char *, char *);

Others have described the process of reading such declarations,
although I do not remember if anyone included the phrase Dennis
Ritchie has used to describe it, namely, "declaration mirrors use".
The above declares PFI as a variable of type "pointer to function
(of two arguments: char *, char *) returning int", and its "use"
might look like this:

result = (*PFI)("hello", "world");

where "result" is some "int" variable. The "use" is a call to a
function, passing two pointer-to-char's, obtaining an int result,
so the function thus called must have return-type "int" and two
"char *" arguments. If you look at the prototype for strcmp, you
find:

int strcmp(const char *, const char *);

which is not quite the same, but uses the same sort of idea -- but
it declares an actual function, rather than a pointer to one. If
the argument types matched, you could then set the define-it-
as-a-variable version of PFI to point to the strcmp function.

Since, without the "typedef" keyword in front, PFI would have
type "pointer to function (...) returning int", putting that
keyword in front means it is an alias for that type.

>2) An example I took from a posting to comp.lang.C from Dan Pop:

> typedef void (*handler_t)(int);
> handler_t signal(int, handler_t);"

>In this case "a pointer to a function returning void and taking an int
>argument" is declared to be a type and aliased to handler_t....???

"Pointer to function (taking int argument) returning void" is
already a type. You can write it any time you like by writing, e.g.:

(void (*)(int))

which is a cast to that (existing) type. Other than that, yes,
the typedef says "instead of declaring handler_t as a variable of
that type, make it an alias for that type."

Note that, in my opinion, the real reason:

handler_t signal(int, handler_t);

is "better" is that it is immediately obvious that the second
parameter to the function has the same type as its return value.
Suppose instead of signal(), we had a function that took a
"pointer-to-function-of-int-returning-void" but itself returned
"void", and we did this:

typedef void (*ptr_to_fn_of_int_ret_void)(int);
void call_it_later(ptr_to_fn_of_int_ret_void);

This is, I claim, *not* any better than just writing:

void call_it_later(void (*)(int));

In both cases, you must pick out the complicated type "pointer to
function of int returning void"; in both cases it only appears
once; and in the first case there is an extra name that adds no
"semantic value".

On the other hand, suppose that, in whatever system you are writing,
you call a lot of things later, but the thing called is always a
function to alert the user that an alarm-timer has expired. For
instance, suppose you are building a fancy sort of alarm clock with
fourteen different alarms: two for each day of the week. In this
case, the int argument might say which alarm is going off (day of
week, and "morning" or "evening" timer). This information -- that
the function to call is a "timer" -- is something that is not
immediately obvious from the type alone. To capture that information,
you can give it a name, along with a comment:

/*
* Timer functions are passed an integer telling them
* "day of week", and "morning timer" vs "evening timer".
*/
typedef void (*timer_function_ptr)(int);

Now, if "call it later" is only ever used to invoke these timers,
you can make that patently obvious by writing:

void call_it_later(timer_function_ptr);

The only problem with this method is that, because typedef
does NOT actually define a new type, the programmer can still
goof up and provide a pointer to some inappropriate function.
Suppose this same fancy alarm clock also has a single-digit
LED display (what for, I have no idea):

void show_single_digit(int val) {
/*
* XXX magic: the display hardware is just memory
* locations defined by display.h. The number to
* be shown must be a single digit.
*/
*SINGLE_DIGIT_DISPLAY_LOCATION = val;
}

Note that show_single_digit() is also a "function (of int) returning
void", i.e., it has exactly the same type as timer functions.
Taking its address produces yet another "ptr to fn (of int) ret
void". So the following "looks right", as far as C's type system
is concerned:

call_it_later(show_single_digit);

But remember, we already said that "call it later" calls the supplied
function with an int that has to have at least fourteen distinct
values (day of week combined with morning-vs-evening). This is
not a single decimal digit (though it might be a single hex digit),
and it is almost certainly wrong to have call_it_later() schedule
a call to show_single_digit(). No C compiler is required to diagnose
this call, though, and few if any will do it.

If you want to *force* a C compiler to diagnose this error, you
must create a new type, which is done with ... yes, not typedef at
all, but rather "struct"! So if you really want fascist-type-checking
of your "timer function pointer" argument to call_it_later, you
might write instead:

struct timer_function_ptr {
void (*fn)(int);
};
void call_it_later(struct timer_function_ptr);

In this case, the struct method is probably overkill, but it serves
to illustrate how typedef fails to define types after all. A struct
-- even one with only a single member -- *does* define a new type.
In C, it is "struct", not "typedef", that defines types.


--
In-Real-Life: Chris Torek, Wind River Systems (BSD engineering)

Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W)

maxw_cc

unread,
Dec 8, 2002, 2:25:12 AM12/8/02
to

My biggest thanks to Chris Torek. He's taken his time to give me (us) a
detailed and quite good explanation on typedef. At last I'm able to
understand the sometimes tricky typedef and its relation to complex
declarations.

Thanks again to Chris and all of you who take the time and knowledge to
keep enriching this oasis for C programmers.

0 new messages