如何通过指针访问结构的第二个成员?

问题描述

我已经看到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 *-它已经是指针类型。每个成员sq的类型为char *。要访问sq成员,您需要取消引用p

(*p).s = ar;    // char * == char *
(*p).q = ar2;   // char * == char *

因此,如果您尝试访问sp指向的第一个字符,则试图通过另一个指针通过指针(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上取消引用。