问题描述
我已经看到struct的第一个地址同时是该结构的第一个成员的第一个地址。现在我想了解的是,为什么我总是需要双指针才能在struct中移动:
#include <stdio.h>
#include <stdlib.h>
struct foo
{
char *s;
char *q;
};
int main()
{
struct foo *p = malloc(sizeof(struct foo));
char ar[] = "abcd\n";
char ar2[] = "efgh\n";
*(char**)p = ar;
*(char**)((char**)p+1) = ar2; //here pointer arithmetic (char**)p+1
printf("%s\n",p->q);
}
问题是,为什么我需要char**
而不是简单的char*
?
我在汇编器中看到的是在简单的char*
情况下,该算法的行为类似于普通的char
。即-> (char*)p+1
的表达式会将地址p
仅移动一个字节(而不是8
,因为地址长8个字节)。但是类型char*
是地址,所以我不明白为什么算术的行为类似于取消引用类型(纯字符->一个字节)。
所以对我而言,唯一的解决方案是添加另一个间接访问char**
,其中指针算术神奇地将8
用作大小。那么为什么在结构中需要这种奇怪的转换呢?
解决方法
您正在做有趣的事情。您应该这样做:
struct foo *p = malloc(sizeof(struct foo));
char ar[] = "abcd\n";
char ar2[] = "efgh\n";
p->s = ar;
p->q = ar2;
,
首先,您正在做的事情有些奇怪。这也是不安全的,因为结构成员之间可能存在填充,并且您的地址计算可能已关闭(在这种情况下可能不正确,但要记住这一点)。
关于为什么需要多个指针...
p
的类型为struct foo *
-它已经是指针类型。每个成员s
和q
的类型为char *
。要访问s
或q
成员,您需要取消引用p
:
(*p).s = ar; // char * == char *
(*p).q = ar2; // char * == char *
因此,如果您尝试访问s
到p
指向的第一个字符,则试图通过另一个指针通过指针(s
)访问一个字符(p
)。 p
不存储s
的第一个字符的地址,它存储存储s
的第一个字符的地址的事物的地址。因此需要将p
强制转换为char **
而不是char *
。
在这一点上,我必须强调“不要这样做”。您不能使用指针安全地遍历struct成员。
引入了->
运算符,以使通过指针访问结构成员的工作量少一些:
p->s = ar; // equivalent to (*p).s = ar
p->q = ar2; // equivalent to (*p).q = ar2
,
由于结构类型的对象的地址等于其第一个成员的地址,因此您可以编写例如
( void * )&p->s == ( void * )p
这是一个演示程序
#include <stdio.h>
#include <stdlib.h>
struct foo
{
char *s;
char *q;
};
int main(void)
{
struct foo *p = malloc(sizeof(struct foo));
printf( "( void * )p == ( void * )&p->s is %s\n",( void * )p == ( void * )&p->s ? "true" : "false" );
return 0;
}
其输出为
true
因此指针p
的值等于数据成员s
的地址。
换句话说,指向数据成员s
的指针等于指针p
。
由于数据成员s
的类型为char *
,所以指向s
的指针的类型为char **
。
要分配指向的对象,您需要将类型struct foo *
的指针p转换为类型char **
。要访问作为数据成员的指针对象,必须取消引用类型char **
的指针。
因此,您拥有
*(char**)p = ar;
现在为数据成员s
(即类型为char *
的指针)分配了数组ar
的第一个元素的地址。
在第二个表达式中,最左侧的转换是多余的
*(char**)((char**)p+1) = ar2;
^^^^^^^^
因为表达式(char**)p+1
已经具有类型char **
。所以你可以写
*((char**)p+1) = ar2;
,
为什么我需要
char**
而不是简单的char*
在指针的使用(赋值的左侧)中,代码需要对象的地址。
*address_of_the_object = object
由于对象是char *
,因此左侧的类型address of the object
必须为char **
。
如何通过指针访问struct的第二个成员?
最好改用明智的方法:
p->q = ar2;
...然后令人费解:
// |-- address of p->q as a char * ----|
*((char **) ((char *)p + offsetof(struct foo,q))) = ar2;
//|------------ address of p->q as a char ** ---|
OP的*(char**)((char**)p+1) = ar2;
不正确,因为它执行了错误的指针数学运算并且假定没有填充。
复杂的方法细节。
要便携式找到struct
内的偏移量,请使用offsetof(struct foo,q)
。它返回字节偏移量,并将说明潜在的填充。将其添加到char *
地址的struct
版本中以进行适当的指针添加,以形成p->q
的地址。该总和为char *
,转换为对象地址的类型。最后,作为分配的一部分在LHS上取消引用。