简体   繁体   中英

Accessing struct member of pointer's address in C

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;
    struct kobject *kobj;
    if (owner && !try_module_get(owner))
        return NULL;
    kobj = kobject_get(&p->kobj);
    if (!kobj)
        module_put(owner);
    return kobj;
}

I understand that this dereferences p, a cdev pointer then accesses its owner member

p->owner // (*p).owner

However, how does this work? It seems like it dereferences the memory address of a cdev pointer then access the kobj member of the pointer itself?

&p->kobj // (*(&p)).kobj

I thought pointers weren't much more than memory addresses so I don't understand how they can have members. And if it was trying to access a member of the pointer itself, why not just do 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.

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 .

->member has higher precedence than & .

&p->kobj

parses as

&(p->kobj)

ie it's taking the address of the kobj member of the struct pointed to by p .

您的操作顺序有误:

&p->kobj // &(p->kobj)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM