Accessing struct member of pointer's address in C

前端 未结 4 1927
[愿得一人]
[愿得一人] 2021-01-07 08:35

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         


        
4条回答
  •  有刺的猬
    2021-01-07 08:44

    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.

提交回复
热议问题