在C中访问指针地址的struct成员

我正在读这本书 ,我在第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

由于p被定义为struct cdev *pp非常多的是“内存地址”,但并不是全部 – 它还附加了一个类型

由于表达式*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 (位覆盖& )之前,这意味着->首先进行计算。

指针变量包含内存地址。 您需要考虑的是如何使用C编程语言以更高级语言编写源代码,然后将其转换为计算机实际使用的机器代码。

C编程语言是一种旨在使计算机硬件比使用汇编代码或机器代码更容易使用的语言。 因此,它具有语言function,可以更轻松地编写比汇编代码更易读,更易于理解的源代码。

当我们在C中声明一个指针变量作为指向类型的指针时,我们告诉编译器是存储在指针中的存储器位置的数据类型。 然而,编译器并不真正知道我们是否说实话。 要记住的关键是实际的内存地址没有类型,它只是一个地址。 一旦编译器将源代码编译成机器代码,任何类型信息都将丢失。

struct是一种模板或模式或模板,用于虚拟覆盖存储区以确定如何解释存储区中的字节。 程序员在处理数据时可以使用更高级别的语言function,而无需了解内存地址和偏移量。

如果将变量定义为结构类型,则会分配足以容纳结构的内存区域,编译器将为您计算成员偏移量。 如果变量被定义为指向内存区域的指针,该内存区域应该再次包含该类型的数据,则编译器将为您计算成员偏移量。 但是,由指针变量包含正确的地址取决于您。

所以如果你有一个类似于以下的结构:

 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) ,我想与struct onesOne成员一起使用? 我可以这样做funcOne(&one.sOne) ,它使用struct _tagStruct变量onesOne成员的地址调用函数funcOne()

机器代码中的典型实现是将变量one地址加载到寄存器中,将偏移量添加到成员sOne ,然后使用此计算的地址调用函数funcOne()

我也可以用指针funcOne(&three->sOne)做类似的事情。

机器代码中的典型实现是将指针变量three内容加载到寄存器中,将偏移量添加到成员sOne ,然后使用此计算的地址调用函数funcOne()

因此,在一种情况下,我们在添加偏移量之前将变量的地址加载到寄存器中;在第二种情况下,我们在添加偏移量之前将变量的内容加载到寄存器中。 在这两种情况下,编译器都使用偏移量,该偏移量通常是从结构的开头到结构的成员的字节数。 在第一个成员的情况下, struct _tagStruct这个偏移量将是零字节,因为它是结构的第一个成员。 对于许多编译器,第二个成员sTwo的偏移量将是两个字节,因为short的大小是两个字节。

然而,编译器可以自由地选择结构的布局,除非另有明确说明,因此在某些计算机上,成员sTwo的偏移量可能是4个字节,以便生成更高效的机器代码。

因此,使用C编程语言可以让我们在某种程度上独立于底层计算机硬件,除非我们有理由真正处理这些细节。

C语言标准指定运算符优先级含义当不同的运算符在语句中混合在一起并且括号不用于指定表达式的精确评估顺序时,编译器将使用这些标准规则来确定如何将C语言表达式转换为正确的机器代码(参见C编程语言的运算符优先级表 )。

点(。)运算符和解除引用( – >)运算符都具有相同的优先级以及运算符的最高优先级。 因此,当您编写诸如&three->sOne类的表达式时,编译器所做的就是将其转换为类似于&(three->sOne) 。 这是使用运算符的地址来计算指针变量three指向的存储区域的sOne成员的地址。

一个不同的表达式是(&three)->sOne实际应该抛出一个编译器错误,因为&three不是一个指向一个带有struct _tagStruct值的内存区域的指针,而是一个指向指针的指针,因为three是指向一个变量的指针输入struct _tagStruct而不是struct _tagStruct类型的变量。

->member优先级高于&

 &p->kobj 

解析为

 &(p->kobj) 

即它采用p指向的结构的kobj成员的地址。

您的操作顺序错误:

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