I\'m reading this book, and I found this code snippet in Chapter 14.
struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
s
->member
has higher precedence than &
.
&p->kobj
parses as
&(p->kobj)
i.e. it's taking the address of the kobj
member of the struct pointed to by p
.
A pointer variable contains a memory address. What you need to consider is how the C programming language is used to write source code in a higher level language that is then converted for you into the machine code actually used by the computer.
The C programming language is a language that was designed to make using the hardware of computers easier than using assembly code or machine code. So it has language features to make it easier to write source code that is more readable and easier to understand than assembly code.
When we declare a pointer variable in C as a pointer to a type what we are telling the compiler is the type of the data at the memory location whose address is stored in the pointer. However the compiler does not really know if we are telling it the truth or not. The key thing to remember is that an actual memory address does not have a type, it is just an address. Any type information is lost once the compiler compiles the source code into machine code.
A struct
is a kind of template or pattern or stencil that is used to virtually overlay a memory area to determine how the bytes in the memory area are to be interpreted. A programmer can use higher level language features when working with data without having to know about memory addresses and offsets.
If a variable is defined as the struct type then a memory area large enough to hold the struct is allocated and the compiler will figure out member offsets for you. If a variable is defined as a pointer to a memory area that is supposed to contain the data for that type again the compiler will figure out member offsets for you. However it is up to you to have the pointer variable containing the correct address.
So if you have a struct something like the following:
struct _tagStruct {
short sOne;
short sTwo;
};
And you then use it such as:
struct _tagStruct one; // allocate a memory area large enough for a struct
struct _tagStruct two; // allocate a memory area large enough for a struct
struct _tagStruct *three; // a pointer to a memory area to be interpreted as a struct
one.sOne = 5; // assign a value to this memory area interpreted as a short
one.sTwo = 7; // assign a value to this memory area interpreted as a
two = one; // make a copy of the one memory area in another
three = &one; // assign an address of a memory area to our pointer
three->sOne = 405; // modify the memory area pointed to, one.sOne in this case
You do not need to worry about the details of the memory layout of the struct and offsets to the struct members. And assigning one struct to another is merely an assignment statement. So this all works at a human level rather than a machine level of thinking.
However what if I have a function, short funcOne (short *inoutOne)
, that I want to use with the sOne
member of the struct one
? I can just do this funcOne(&one.sOne)
which calls the function funcOne()
with the address of the sOne
member of the struct _tagStruct
variable one
.
A typical implementation of this in machine code is to load the address of the variable one
into a register, add the offset to the member sOne
and then call the function funcOne()
with this calculated address.
I could also do something similar with a pointer, funcOne(&three->sOne)
.
A typical implementation of this in machine code is to load the contents of the pointer variable three
into a register, add the offset to the member sOne
and then call the function funcOne()
with this calculated address.
So in one case we load the address of a variable into a register before adding the offset and in the second case we load the contents of a variable into a register before adding the offset. In both cases the compiler is using an offset which is usually the number of bytes from the beginning of the struct to the member of the struct. In the case of the first member, sOne
of struct _tagStruct
this offset would be zero bytes since it is the first member of the struct. For many compilers the offset of the second member, sTwo
, would be two bytes since the size of a short
is two bytes.
However the compiler is free to make choices about the layout of a struct unless explicitly told otherwise so on some computers the offset of member sTwo
may be four bytes in order to generate more efficient machine code.
So using the C programming language allows us some degree of independence from the underlying computer hardware unless there is some reason for us to actually deal with those details.
The C language standard specifies operator precedence meaning when different operators are mixed together in a statement and parenthesis are not used to specify an exact order of evaluation on the expression then the compiler will use these standard rules to determine how to turn the C language expression into the proper machine code (see Operator precedence table for the C programming language ).
Both the dot (.) operator and the dereference (->) operator have equal precedence as well as the highest precedence of the operators. So when you write an expression such as &three->sOne
then what the compiler does is turn it into an express that looks like &(three->sOne)
. This is using the address of operator to calculate an address of the sOne
member of the memory area pointed to by the pointer variable three
.
A different expression would be (&three)->sOne
which actually should throw a compiler error since &three
is not a pointer to a memory area holding a struct _tagStruct
value but is instead a pointer to a pointer since three
is a pointer to a variable of type struct _tagStruct
and not a variable of type struct _tagStruct
.
You have the order of operations wrong:
&p->kobj // &(p->kobj)
As per p
being defined as struct cdev *p
, p
is very much a "memory address" but that's not all it is - it also has a type attached to it.
Since the expression *ptr
is "the object pointed to by ptr
", that also has the type attached, so you can logically do (*ptr).member
.
And, since ptr->member
is identical to (*ptr).member
, it too is valid.
Bottom line is, your contention that "pointers [aren't] much more than memory addresses" is correct. But they are a little bit more :-)
In terms of &ptr->member
, you seem to be reading that as (&ptr)->member
, which is not correct.
Instead, as per C precedence rules, it is actually &(ptr->member)
, which means the address of the member of that structure.
These precedence rules are actually specified by the ISO C standard (C11 in this case). From 6.5 Expressions
, footnote 85
:
The syntax specifies the precedence of operators in the evaluation of an expression, which is the same as the order of the major subclauses of this subclause, highest precedence first.
And, since 6.5.2 Postfix operators
(the bit covering ->
) comes before 6.5.3 Unary operators
(the bit covering &
), that means ->
evaluates first.