C结构是否将其成员保存在连续的内存块中?

时间:2020-01-21 22:26:39

标签: c

假设我的代码是:

typedef stuct {
  int x;
  double y;
  char z;
} Foo;

在内存中xyz会紧挨着吗?指针算术可以在它们上“迭代”吗? 我的C生锈了,所以我不能完全通过正确的程序来测试它。 这是我的完整代码。

#include <stdlib.h>
#include <stdio.h>

typedef struct {
  int x;
  double y;
  char z;
} Foo;


int main() {
  Foo *f = malloc(sizeof(Foo));
  f->x = 10;
  f->y = 30.0;
  f->z = 'c';
  // Pointer to iterate.
  for(int i = 0; i == sizeof(Foo); i++) {
    if (i == 0) {
      printf(*(f + i));
    }
    else if (i == (sizeof(int) + 1)) {
      printf(*(f + i));
    }
    else if (i ==(sizeof(int) + sizeof(double) + 1)) {
      printf(*(f + i));
    }
    else {
      continue;
    }
  return 0;
}

6 个答案:

答案 0 :(得分:13)

否,不能保证struct成员在内存中是连续的。

从C标准的6.7.2.1节第15点开始(第115页here):

结构对象中可能存在未命名的填充,但在其开头没有。

大多数时候,类似:

struct mystruct {
    int a;
    char b;
    int c;
};

确实与sizeof(int)对齐,如下所示:

 0  1  2  3  4  5  6  7  8  9  10 11
[a         ][b][padding][c          ]

答案 1 :(得分:10)

是,不是。

是的,结构的成员分配在连续的内存块中。在您的示例中,Foo类型的对象占用sizeof (Foo)个连续字节的内存,并且所有成员都在该字节序列内。

但是,不能保证成员本身彼此相邻。在任何两个成员之间或在最后一个成员之后可以有填充字节。该标准确实保证了第一个定义的成员的偏移量为0,并且所有成员均按其定义的顺序分配(这意味着有时可以通过对成员进行重新排序来节省空间)。

因此,您不能(直接)遍历结构的成员。如果要执行此操作,并且所有成员都属于同一类型,请使用数组。

可以使用<stddef.h>中定义的offsetof宏来确定(非位域)成员的字节偏移,有时使用该宏可能会很有用建立一个可用于遍历结构成员的数据结构。但这很乏味,而且比简单地按名称引用成员更有用-尤其是如果它们具有不同的类型。

答案 2 :(得分:1)

x,y和z在内存中会紧挨着吗?

不。结构内存分配布局取决于实现-无法保证结构成员彼此相邻。原因之一是内存填充,即

指针算术可以在它们上“迭代”吗?

不。您只能对相同类型的指针进行指针算术。

答案 3 :(得分:1)

x,y和z在内存中彼此相邻吗?

可能是,但不一定如此。 ISO C标准不要求在结构中放置元素。

通常,编译器会将元素放置在对其编译的体系结构“最佳”的某个偏移量处。因此,在32位CPU上,默认情况下,大多数编译器会将元素的偏移量设置为4的倍数(这样可以最有效地进行访问)。但是,大多数编译器也可以指定不同的位置(对齐方式)。

所以,如果您有类似的东西:

<div>
  <ul class="progressbar">
    <li id="0"></li>
    <li id="2"></li>
    <li id="4"></li>
    <li id="6"></li>
    <li id="8"></li>
    <li id="10"></li>
  </ul>
</div>

然后,在大多数具有默认选项的32位编译器上,struct X { uint8_t a; uint32_t b; }; 的偏移量将是a,但是0的偏移量将是b

指针算术可以在它们上“迭代”吗?

与您示例中的代码不同。定义了指向结构的指针的指针算术,以使用结构的 size 加/减地址。所以,如果您有:

4

然后struct X a[2]; struct X *p = a;

要在元素上“迭代”,您需要将p+1 == a+1强制转换为p,然后逐个元素地添加元素的偏移量(使用uint8_t*标准宏)

答案 4 :(得分:0)

这取决于编译器决定的填充(这受目标体系结构的要求和优势的影响)。 C标准确实保证在struct的第一个成员之前没有填充,但是在那之后,您不能承担任何责任。但是,如果sizeofstruct确实等于其每个构成类型的sizeof,则没有填充。

您可以不使用编译器特定的指令来强制填充。在MSVC上,是:

#pragma pack(push, 1)
// your struct...
#pragma pack(pop)

GCC具有__attribute__((packed))的等效效果。

答案 5 :(得分:0)

在此问题上尝试使用指针算法存在多个问题。

第一个问题,正如在其他答案中提到的那样,是整个结构中可能存在填充,从而无法进行计算。

C11工作草案6.7.2.1 p15:(粗体强调

在结构对象中,非位域成员以及其中位域的单位 居住的地址按声明的顺序增加。指向一个的指针 经过适当转换的结构对象指向其初始成员(或者该成员是 位域,然后到它所在的单元),反之亦然。 可能未命名 填充在结构对象中,但不在其开头。

第二个问题是指针算术是以所指向类型的大小的倍数完成的。对于结构,如果在结构的指针上加1,则指针将指向该结构之后的对象。使用示例结构Foo

Foo x[3];
Foo *y = x+1; // y points to the second Foo (x[1]), not the second byte of x[0]

6.5.6 p8:

将具有整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,以使结果和原始下标的差值 数组元素等于整数表达式。换句话说,如果表达式 P 指向数组对象的第 i 个元素,则表达式(P)+ N (等效, N +(P))和(P)-N (其中 N 的值为 n )指向分别提供数组对象的第 i + n -th和 i - n -th个元素它们存在。

第三个问题是,执行指针算术以使结果指向对象末尾超过一个以上会导致未定义的行为,将指针指向通过对象算术获得的对象末尾之后的一个元素也是如此。因此,即使您有一个包含三个int且中间没有填充的结构,并使用了指向第一个int的指针并将其递增以指向第二个int,对其取消引用将导致未定义的行为。

更多来自6.5.6的内容:( 粗体强调重点

此外,如果表达式 P 指向数组对象的最后一个元素,则表达式(P)+1 指向数组对象的最后一个元素。 数组对象,如果表达式 Q 指向数组对象的最后一个元素,则表达式(Q)-1 指向数组对象的最后一个元素。如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则求值不应产生溢出;否则,行为是不确定的。 如果结果指向数组对象的最后一个元素之后,则不应将其用作被评估的一元*运算符的操作数。

第四个问题是,将一种类型的指针取消引用为另一种类型会导致未定义的行为。这种对类型进行修饰的尝试通常称为严格混叠违规。下面是一个示例,该示例通过严格混叠冲突来定义行为,即使数据类型的大小相同(假定为4字节int和float)且对齐方式很好:

int x = 1;
float y = *(float *)&x;

6.5 p7:

一个对象的存储值只能由具有以下之一的左值表达式访问 以下类型:

  • 与对象的有效类型兼容的类型

  • 与有效类型兼容的类型的限定版本

  • 与签名或未签名类型相对应的类型 到对象的有效类型

  • 一种类型,它是对应于a的有符号或无符号类型 有效类型的对象的限定版本,

  • 包括以下类型之一的聚合或联合类型 成员之间的上述类型(包括递归 子集合或包含的联盟的成员),或

  • 字符类型。

摘要: 不,C结构不一定将其成员保存在连续的内存中,即使这样做,指针算法仍然无法执行您希望使用指针算术进行的操作。

相关问题