C结构点运算符究竟做了什么(低级透视)?

时间:2016-02-23 21:56:34

标签: c struct

我对C中的结构有疑问。所以当你创建一个结构时,你实际上是在定义一块内存的框架。因此,当您创建结构的实例时,您正在创建一个内存块,以便它能够容纳一定数量的元素。

但是,我对点运算符的作用感到有些困惑。如果我有struct Car并且有一个名为GasMileage的成员(int成员),我可以通过执行类似的操作来获取GasMileage的值,< / p>

int x = CarInstance.GasMileage;

然而,我对这个点运算符实际发生的事情感到困惑。点运算符是否只是作为基址的偏移量?它究竟是如何推断它是一个int?

我想我对幕后发生的事情很好奇。通过做其他事情可以引用GasMileage吗?如

int *GasMileagePointer = (&carInstance + offsetInBytes(GasMileage));
int x = *GasMileage

这只是我快速弥补的事情。我一直在努力寻找一个好的解释,但似乎没有什么比将点运算符视为魔术更能解释它了。

4 个答案:

答案 0 :(得分:5)

当您使用.运算符时,编译器会根据其前面的字段(和填充)的大小将其转换为struct内的偏移量。

例如:

struct Car {
    char model[52];
    int doors;
    int GasMilage;
};

假设int是4个字节且没有填充,model的偏移量为0doors的偏移量为52,偏移量GasMilage的数字是56。

因此,如果你知道成员的偏移量,你可以得到一个指向它的指针:

int *GasMileagePointer = (int*)((char *)&carInstance + offsetInBytes(GasMile));

强制转换为char *是必要的,因此指针算法一次只能传输1个字节而不是1 sizeof(carInstance)。然后需要将结果转换为正确的指针类型,在本例中为int *

答案 1 :(得分:2)

是的,点运算符只是从结构的底部应用一个偏移量,然后访问该地址的值。

int x = CarInstance.GasMileage;

相当于:

int x = *(int *)((char*)&CarInstance + offsetof(Car, GasMileage));

对于其他类型T的成员,唯一的区别是广告(int *)变为(T *)

答案 2 :(得分:1)

点运算符只需选择成员。

由于编译器具有关于成员的类型(以及因此 size )的信息(实际上是所有成员),因此它知道成员从一开始的偏移量结构,并可以生成适当的指令。它可以生成基本+偏移访问,但它也可以直接访问成员(或者甚至将其缓存在寄存器中)。编译器具有所有这些选项,因为它在编译时具有所有必要的信息。

如果没有,例如<​​strong>不完整类型,则会出现编译时错误。

答案 3 :(得分:0)

当它工作时,“。” “。”的行为运算符等效于获取结构的地址,通过成员的偏移量对其进行索引,并将其转换为成员类型的指针,并将其解除引用。但是,该标准规定,在某些情况下,不能保证不起作用。例如,给定:

struct s1 {int x,y; }
struct s2 {int x,y; }
void test1(struct s1 *p1, struct s2 *p2)
{
  s1->x++;
  s2->x^=1;
  s1->x--;
  s2->x^=1;
}

编译器可能会认为没有合法的方式p1-&gt; x和p2-&gt; x 可以识别相同的对象,因此它可以重新排序代码以便++ - 对s1-> x取消的操作,以及对s2-> x取消的^ = 1操作, 从而留下一个什么都不做的功能。

请注意,使用联合时行为不同,因为给定:

union u { struct s1 v1; struct s2 v2; };

void test2(union u *uv)
{
  u->v1.x^=1;
  u->v2.x++;
  u->v1.x^=1;
  u->v2.x--;
}

common-initial-subsequence规则表示因为u-> v1和u-> v2 从相同类型的字段开始,访问u-&gt; v1中的这样一个字段 相当于访问u-> v2中的相应字段。因此,一个 编译器不允许重新排序。另一方面,给定

void test1(struct s1 *p1, struct s2 *p2);
void test3(union u *uv)
{
  test1(&(u.v1), &(u.v2));
}

u.v1和u.v2以匹配字段开头的事实并不能防范 编译器假设指针不会别名。

请注意,某些编译器提供了强制生成代码的选项 成员访问总是与上述指针等效 操作。对于gcc,选项为-fno-strict-alias。如果需要代码 访问不同结构类型的公共初始成员,省略它 切换可能导致一个人的代码在奇怪,怪异和不可预测的情况下失败 方式。