我正在阅读this book,我在第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;
}
我理解这个解引用p,然后cdev指针访问其所有者成员
p->owner // (*p).owner
然而,这是如何工作的?它似乎取消引用cdev指针的内存地址然后访问指针本身的kobj成员?
&p->kobj // (*(&p)).kobj
我认为指针不仅仅是内存地址,所以我不明白他们如何拥有成员。如果它试图访问指针本身的成员,为什么不只是p.kobj
?
答案 0 :(得分:4)
根据p
被定义为struct cdev *p
,p
是一个"内存地址"但它不是所有 - 它还附加了类型。
由于表达式*ptr
是" ptr
"指向的对象,也附加了类型,因此您可以在逻辑上执行(*ptr).member
。
并且,由于ptr->member
与(*ptr).member
相同,因此它也是有效的。
最重要的是,你的论点是指针[不是很多 是正确的。但他们更多的是小: - )
就&ptr->member
而言,您似乎将其视为(&ptr)->member
,这是不正确的。
相反,根据C优先规则,它实际上是&(ptr->member)
,这意味着该结构成员的地址。
这些优先规则实际上是由ISO C标准(在本例中为C11)指定的。来自6.5 Expressions
,脚注85
:
语法指定运算符在表达式求值中的优先级,与本子条款的主要子条款的顺序相同,优先级最高。
并且,由于6.5.2 Postfix operators
(覆盖->
的位)在6.5.3 Unary operators
之前(覆盖&
的位),这意味着->
首先评估。< / p>
答案 1 :(得分:2)
指针变量包含内存地址。您需要考虑的是如何使用C编程语言以更高级语言编写源代码,然后将其转换为计算机实际使用的机器代码。
C编程语言是一种旨在使计算机硬件比使用汇编代码或机器代码更容易使用的语言。因此它具有语言功能,可以更容易地编写比汇编代码更易读,更易于理解的源代码。
当我们在C中声明一个指针变量作为指向类型的指针时,我们告诉编译器是存储在指针中的内存位置的数据类型。然而,编译器并不真正知道我们是否说实话。要记住的关键是实际的内存地址没有类型,它只是一个地址。一旦编译器将源代码编译成机器代码,任何类型信息都将丢失。
struct
是一种模板或模式或模板,用于虚拟覆盖存储区以确定如何解释存储区中的字节。程序员在处理数据时可以使用更高级别的语言功能,而无需了解内存地址和偏移量。
如果将变量定义为结构类型,则会分配足以容纳结构的内存区域,编译器将为您计算成员偏移量。如果变量被定义为指向内存区域的指针,该内存区域应该再次包含该类型的数据,则编译器将为您计算成员偏移量。但是,由指针变量包含正确的地址取决于你。
因此,如果你有一个类似于以下的结构:
struct _tagStruct {
short sOne;
short sTwo;
};
然后你使用它,如:
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
您不必担心结构的内存布局的细节和结构成员的偏移。将一个结构分配给另一个结构只是一个赋值语句。所以这一切都在人类层面而不是机器层面进行思考。
但是如果我有一个函数short funcOne (short *inoutOne)
,我想与结构sOne
的{{1}}成员一起使用该怎么办?我可以执行此操作one
,使用funcOne(&one.sOne)
变量funcOne()
的{{1}}成员的地址调用函数sOne
。
机器代码中的典型实现是将变量struct _tagStruct
的地址加载到寄存器中,将偏移量添加到成员one
然后调用函数one
使用此计算地址。
我也可以使用指针sOne
做类似的事情。
机器代码中的典型实现是将指针变量funcOne()
的内容加载到寄存器中,将偏移量添加到成员funcOne(&three->sOne)
然后调用具有此计算地址的函数three
。
因此在一种情况下,我们在添加偏移量之前将变量的地址加载到寄存器中,在第二种情况下,我们将变量的内容加载到寄存器中在添加偏移量之前。在这两种情况下,编译器都使用偏移量,该偏移量通常是从结构的开头到结构的成员的字节数。在第一个成员的情况下,sOne
funcOne()
这个偏移量将是零字节,因为它是结构的第一个成员。对于许多编译器,第二个成员sOne
的偏移量将是两个字节,因为struct _tagStruct
的大小是两个字节。
然而,编译器可以自由选择结构的布局,除非另有明确说明,因此在某些计算机上,成员sTwo
的偏移量可能是4个字节,以便生成更高效的机器代码。
因此,使用C编程语言可以让我们在某种程度上独立于底层计算机硬件,除非我们有理由真正处理这些细节。
C语言标准指定运算符优先级含义当不同的运算符在语句中混合在一起并且括号不用于指定表达式的精确评估顺序时,编译器将使用这些标准规则来确定如何转换C语言表达到正确的机器代码中(参见Operator precedence table for the C programming language)。
点(。)运算符和解除引用( - &gt;)运算符都具有相同的优先级以及运算符的最高优先级。因此,当您编写诸如short
之类的表达式时,编译器所做的就是将其转换为类似sTwo
的表达式。这是使用运算符的地址来计算指针变量&three->sOne
指向的内存区域的&(three->sOne)
成员的地址。
另一个表达式是sOne
实际应该抛出编译器错误,因为three
不是指向保存(&three)->sOne
值的内存区域的指针,而是指向指针的指针因为&three
是指向struct _tagStruct
类型变量的指针,而不是three
类型的变量。
答案 2 :(得分:0)
->member
的优先级高于&
。
&p->kobj
解析为
&(p->kobj)
即。它取kobj
指向的结构的p
成员的地址。
答案 3 :(得分:0)
您的操作顺序错误:
&p->kobj // &(p->kobj)