Structure (Newbie)

R

Ronny Mandal

Hi!

Assume that we have a structure called myStruct:

What is the the difference of referencing the members like this:

myStruct.foo

and

myStruct->bar

is it that foo is a member-variable, and bar is a member-variable which is a
pointer?

like

myStruct
{
int foo = 2;
int *bar = aValue;
}


Thanks!

RM
 
K

Kevin Goodsell

Ronny said:
Hi!

Assume that we have a structure called myStruct:

I'm assuming you mean an instance of a struct type.
What is the the difference of referencing the members like this:

myStruct.foo

and

myStruct->bar

The first is legal. The second is not.
is it that foo is a member-variable, and bar is a member-variable which is a
pointer?

Definitely not.
like

myStruct
{
int foo = 2;
int *bar = aValue;
}

That's a rather long way from being valid C.

Assuming you actually meant something like this:

struct myStruct
{
int foo;
int *bar;
} my_struct = {2, NULL};

Given this, consider the following:

struct myStruct *my_ptr = &my_struct;

/* dot is for direct access: */
my_struct.foo;
my_struct.bar;

/* arrow is for indirect access: */
my_ptr->foo;
my_ptr->bar;

/* alternative method of indirect access,
equivalent to arrow: */
(*my_ptr).foo;
(*my_ptr).bar;

The awkwardness of the last example is the reason the -> operator was
introduced.

-Kevin
 
R

Ronny Mandal

Thanks for clarifying words.

But regarding pointers, &foo returns the address of foo. And we can give
this address to a pointer, like this (according to the your reply):

int *bar = &foo

I thought that when placing a * before the variable, we manipulated the
contents of the pointer.

So the right syntax would be:

int foo = 5;
int *bar;

bar = &foo;

prinft( bar ) produces something like 32768...

printf( *bar ) would yield 5

I must've mis-percepted something along the way.

And: Is there a difference of accessing a pointer that is inside a structure
versus accessing a normal variable. (I assume no, because I've read that
pointers themselves are variables)


Thanks!

Regards,

Ronny Mandal
 
K

Kevin Goodsell

Ronny said:
Thanks for clarifying words.

But regarding pointers, &foo returns the address of foo.

It's better to say it /yields/ the address of foo, to avoid confusion
with functions.
And we can give
this address to a pointer, like this (according to the your reply):

int *bar = &foo

I thought that when placing a * before the variable, we manipulated the
contents of the pointer.

That's true in expressions involving the pointer. The above is a
declaration. &foo is used to initialize bar. In initializations, it's
always the thing being created that gets the value - in this case, the
pointer 'bar'.
So the right syntax would be:

int foo = 5;
int *bar;

bar = &foo;

That's basically equivalent to:

int foo = 5;
int *bar = &foo;
prinft( bar ) produces something like 32768...

I assume you mean something like:

printf("%p", (void *)bar);

This will print the address stored in bar using some
implementation-defined format.
printf( *bar ) would yield 5

I assume you mean:

printf("%d", *bar);

This will print 5.
I must've mis-percepted something along the way.

And: Is there a difference of accessing a pointer that is inside a structure
versus accessing a normal variable. (I assume no, because I've read that
pointers themselves are variables)

The following contains a few pointers that are not variables:

const int *p = 0;
int a[20];
int *q = a;

/* these expressions yield pointers that are not variables:
p
a
(void *)q
q + 1
(char *)0
*/

actually, 'a' isn't always a pointer, but it usually is.

You access pointer members of a struct exactly like non-pointer members.

-Kevin
 
B

Barry Schwarz

Hi!

Assume that we have a structure called myStruct:

What is the the difference of referencing the members like this:

myStruct.foo

and

myStruct->bar

is it that foo is a member-variable, and bar is a member-variable which is a
pointer?

snip

The . operator requires the left hand operand to have type struct.

The -> operator requires the left hand operand to have type pointer to
struct.

In either case, the right hand operand is a member of the struct in
question. The type of this member has no affect on which operator you
use. That is determined exclusively by the type of the left hand
operator.


<<Remove the del for email>>
 
R

Ronny Mandal

The . operator requires the left hand operand to have type struct.

The -> operator requires the left hand operand to have type pointer to
struct.

So the implementors did it this way just because the compiler should be able
to distinguish between pointers to structures and structures? (i.e if i have
a pointer to a structure called myStruct, and try to access it members with
the dot operator - the compiler would treat it like a structure.) But since
pointers are variables, then ...ok I see! A structure is a structure, and a
pointer to it is a variable!

I assume.

RM
 
P

pete

Ronny said:
So the implementors did it this way just because the
compiler should be able to distinguish between pointers
to structures and structures?
(i.e if i have a pointer to a structure called myStruct,
and try to access it members with the dot operator
- the compiler would treat it like a structure.) But since
pointers are variables, then ...ok I see!
A structure is a structure, and a
pointer to it is a variable!

A pointer can be a variable, but it doesn't have to be.
A struct type object can be a variable too.
There is nothing unusual about a pointer, pointing to a variable.

structure.member

is the same as

(&structure)->member

(&structure), is a pointer, but not a variable.


If you have

pointer = &structure;

then

pointer -> member

is the same as

(*pointer).member
 
J

John Bode

Ronny Mandal said:
So the implementors did it this way just because the compiler should be able
to distinguish between pointers to structures and structures? (i.e if i have
a pointer to a structure called myStruct, and try to access it members with
the dot operator - the compiler would treat it like a structure.) But since
pointers are variables, then ...ok I see! A structure is a structure, and a
pointer to it is a variable!

I assume.

RM

A struct is a struct and a pointer is a pointer. Both may be variables.
 
C

Chris Torek

So the implementors did it this way just because the compiler should
be able to distinguish between pointers to structures and structures?

The answer ultimately boils down to "Dennis Ritchie did it this way
because he liked it that way", more or less.
(i.e if i have a pointer to a structure called myStruct, and try
to access it members with the dot operator - the compiler would
treat it like a structure.)

There *are* languages that have both structured types and pointers
(including pointers to structured types), *and* do precisely this.
(The one I used myself is Mesa.) If C did this, one would be able
to write:

struct S {
...
int member;
...
};
...
struct S var;
struct S *ptr;
...
ptr = &var;
...
use(ptr.member); /* NOT ALLOWED IN C, but OK in Mesa */

The compiler would see that "ptr" has type "pointer to struct S",
and would treat the "." operator as meaning "follow the pointer to
the struct, then access the named member." But C does not do this:
the "." operator *demands* that its left hand operand have some
struct (or union) type -- such as "struct S" -- and only then does
the "access the named member" part. C then has a second operator,
"->", that *demands* that its left hand operand have some pointer
to struct or union type -- such as "pointer to struct S" -- and
only then does the "follow the pointer to access the named member"
part.

The two actions are slightly different: one follows a pointer and
accesses a named member, while the other simply access a named
member. But both have a common element ("access named member")
and it is clear which action to perform: "if pointer to struct S,
follow then access; if struct S, just access; if neither pointer
to struct S nor actual struct S, error". As such, two separate
operators are not *required*. They just happen to be what Dennis
did, probably because he liked it that way.
But since pointers are variables,

Pointers *can be* (stored in) variables. Pointers themselves are
just values of type "pointer to <some other type>". Once you store
such a pointer in a variable, that variable is just like any other
variable. In particular, you can take its address:

struct S *ptr;
int error;

extern int new_S(struct S **);
...
error = new_S(&ptr);

Here new_S() takes the address of a variable of type "pointer to
struct S", and fills in the variable. It needs the address of that
variable because C passes arguments by value (every time, even for
arrays: it is just that the "value" of an array is quite peculiar).
The address of "ptr", &ptr, has type "pointer to (pointer to struct
S)", which in C is spelled "struct S *".

Note that new_S has to follow the address you gave it, to find the
actual object of type "pointer to struct S" to fill in:

int new_S(struct S **retp) {
struct S *tmp;

tmp = malloc(sizeof *tmp);
if (tmp == NULL)
return FAILED_TO_ALLOCATE_MEMORY;
tmp->member = 42;
*retp = tmp;
return OK;
}

We could replace each "tmp" with "*retp":

*retp = malloc(sizeof **retp);
if (*retp == NULL)
return FAILED_TO_ALLOCATE_MEMORY;
(*retp)->member = 42;
*retp = *retp; /* obviously redundant! */

but I prefer the version with "tmp", all the *retp's are repetitive
and because (*retp)->member is awkward. Note, however, that these
two versions do something slightly different too: if new_S returns
FAILED_TO_ALLOCATE_MEMORY, the version with "tmp" leaves *retp
unchanged. The version without it has set *retp to NULL. If the
call looks like:

error = new_S(&ptr);

then one version leaves "ptr" unchanged, and possibly uninitialized,
on error, while the other leaves it set to NULL. You can of course
modify the version with "tmp":

if (tmp == NULL) {
*retp = NULL;
return FAILED ...

or even just:

*retp = tmp = malloc(sizeof *tmp);
if (tmp == NULL) ...
tmp->member = 42;
/* *retp is already set */
return OK;

(which is probably how I would write it, if I wanted "ptr" changed
even on failure).

There are a number of key things to note here:

- Each pointer has a type. Each object (roughly, "variable")
also has a type.

- Any pointer variable can store a pointer value of the type
given by its declaration.

- Pointer values coming from malloc() are "special": if you
supply the correct size, they are valid for use as an object
of that size. For this reason, they have the funky/bizarre
type "void *", which gets converted automatically by assignments.

The objects allocated by malloc() also have a special lifetime:
they last until explicitly free()d. All other C objects have
either "static" duration -- they live as long as the program
runs -- or "automatic", allocated by entry to their "{}" block
and deallocated when the block ends.

- The "&" operator takes the name of an object (such as an
ordinary value) and produces a value of pointer type, pointing
to the object. The type of this pointer depends on the type
of the object, and basically just has "pointer to" shoved in
front of the English-language expansion of the C type. If
the object has type "T", the pointer has type "pointer to T".

- The "*" operator follows a pointer to whatever object it
points to. It must point to some object for this to work.
Naturally enough, the object should have the type indicated by
the pointer: following a "pointer to T" gives you an object
of type "T". (I am deliberately ignoring pointers to
functions here.)

- A valid pointer value is either NULL or the address of some
object somewhere.

Everything else simply follows from this. In new_S(), "retp" names
an object of type "pointer to pointer to struct S". Assuming retp
has a valid value, it points to an object of type "pointer to struct
S" (or in C, "struct S *"). The object at *retp need not have a
valid value to start with, because we are going to overwrite it.
(Some programmers prefer to make sure it has a valid value anyway,
e.g., by setting it to NULL when first creating it. But new_S()
does not depend on this.)

The call to malloc() asks for enough bytes for a "struct S". This
either succeeds, getting a valid "struct S" that will exist until
explicitly free()d, or fails and returns NULL. The return value
has type "void *", but we stick this into "tmp", which has type
"struct S *", thus converting it. We then check to see if the
result was NULL (or, in the last version of new_S(), copy tmp to
*retp first, then check for NULL). If tmp is NULL, we return a
failure error code, for whoever called new_S() to deal with.

If malloc() succeeded, on the other hand, we use "tmp" -- which has
type "pointer to struct S" and now points to a valid object -- with
the "->" operator, to get to "tmp->member". This is just shorthand
for (*tmp).member. The *tmp action follows the pointer in tmp
-- which as we know, is a valid pointer pointing to a real object
-- and accesses the object. The object has type "struct S", so
this gives us the "struct S" that the "." operator requires, and
the ".member" part then accesses the structure's "member" field,
which we set to 42 (The Answer, according to the Hitchhiker's Guide
series).

Last, we set *retp (if we have not already done so) and return OK,
meaning "new_S succeeded". This tells our caller that it is OK to
use *retp, which in the example call, names the same object as
"ptr":

error = new_S(&ptr);

If "retp" is &ptr, then "*retp" must be "ptr". The * and &
effectively cancel each other out. So if error == OK, meaning
"nothing went wrong", ptr is now set to a valid value from malloc(),
and ptr->member is set to 42.

(A function like "new_S" practically begs for one named "release_S"
or "discard_S" or "delete_S" or some such. In this case, such a
function need only call free(), but with more complicated data
structures, you might do more in the S-destruction function. Note
also that the names "S_new" and "S_delete" might be better in some
ways: all "struct S" operations might have names that start with S_.)
 
B

Barry Schwarz

So the implementors did it this way just because the compiler should be able
to distinguish between pointers to structures and structures? (i.e if i have

Since you have to declare the type of an object before you use that
object, then in fact you tell the compiler whether object x is a
struct or a pointer to struct.
a pointer to a structure called myStruct, and try to access it members with
the dot operator - the compiler would treat it like a structure.) But since

No, the compiler is obligated to generate a diagnostic telling you the
left hand operator has the wrong type to be used with the dot
operator.
pointers are variables, then ...ok I see! A structure is a structure, and a
pointer to it is a variable!

No, structures are variables also (known as aggregates). A structure
is a structure and a pointer is a pointer. Some pointers point to
structures; others don't. A structure contains members, some or all
of which may be pointers. Such a pointer of the proper type can even
point to the structure it is contained in.
I assume.

Why assume when you can look it up in a text or the freely available
draft of the standard?

What is your real question? You are beating around the bush trying to
coerce some concept into a pre-ordained opinion.


<<Remove the del for email>>
 
R

Ronny Mandal

Barry Schwarz said:
What is your real question? You are beating around the bush trying to
coerce some concept into a pre-ordained opinion.

Too bad for you, that you were the only one with that perception.

Indeed, thanks to the other guys for the interesting answers.

-RM
 
C

Chris Torek

The address of "ptr", &ptr, has type "pointer to (pointer to struct
S)", which in C is spelled "struct S *".

There is a small but important typographic error here: the type
"pointer to pointer to struct S" is spelled "struct S **".
 

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

Members online

Forum statistics

Threads
474,122
Messages
2,570,717
Members
47,283
Latest member
VonnieEwan

Latest Threads

Top