最好用示例来解释。
typedef struct s_ {
int a, b;
} s;
int add(s* l, s* r) { return l->a + l->b + r->a + r->b; }
void init(s* v) {
v->a = 1;
v->b = 2;
}
int array_like() {
// allocate enough space for s[2]
char *p = malloc(2 * sizeof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + sizeof(s));
init(p1);
init(p2);
return add(p1, p2);
}
int array_skip() {
// allocate enough space for s[3] and init only [0] and [2]
char *p = malloc(3 * sizeof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + 2 * sizeof(s));
init(p1);
init(p2);
return add(p1, p2);
}
int half_gap() {
// allocate enough space for 2.5 s objects and lay them
// out like [s1][gap of alignof(s) bytes][s2]
char *p = malloc(2 * sizeof(s) + _Alignof(s));
s *p1 = (s *)p;
s *p2 = (s *)(p + sizeof(s) + _Alignof(s));
init(p1);
init(p2);
return add(p1, p2);
}
具体而言,考虑一个典型的平台,其中sizeof(s) == 8
和alignof(s) == 4
–尽管该问题应同样适用于具有不同值的平台。
[最后三个函数array_like
,array_skip
和half_gap
都执行类似的功能:它们在[]分配的存储空间内创建两个s
类型的对象(struct
包含两个int
)。 C0]。所有这三个对象都将第一个malloc
对象放在存储区的开头。它们的区别仅在于放置第二个对象的位置:
s
将其直接放置在第一个对象之后,即在偏移量8处,因此对于指向区域起点的指针array_like
,对象将位于s* p
和p[0]
。p[1]
将其放置在第一个对象的末尾array_skip
字节之后,即偏移量16,因此对于指向区域开始的指针sizeof(s)
,对象将位于s* p
和p[0]
。p[2]
将其放置在第一个对象末尾的half_gap
个字节,或第一个对象后的4个字节,即从存储开始的12个字节。请注意,此对象仍然在4个字节的边界上正确对齐,但是它没有从第一个偏移_Alignof(s)
个字节的integral个数。您无法像前两种情况那样使用类似数组的符号来表示第二个对象的位置。[之后,每个函数都写入两个对象的两个成员,然后从它们中读取。
其中哪些函数是合法的C11,并且所有合法函数都可以保证返回期望值6吗?
您问题中的代码是正确的。且仅当指针类型与要转换的类型正确对齐时,才定义指针类型转换。
[不保证将sizeof(s)
转换为任何类型以供将来使用时将其分配到分配的块中。因此,我认为该参数无效)。由于要求数组在元素之间没有填充,我们可以得出结论,malloc
必须是void *
的倍数,因此所有整数表达式都是sizeof(T)
的倍数,因此所有指针有问题的位置正确对齐。
关于术语,
对象在C中表示“存储区域”。因此,由malloc分配的整个空间是一个对象,它的任何连续子集也是如此。
使用赋值运算符修改一个对象,而不创建它。在分配的空间上使用赋值运算符时,它将设置写入的字节的有效类型
。 C11 6.5 / 6(又称为严格别名规则)定义了“有效类型”的含义。_Alignof(T)
函数如下所示:_Alignof(T)
然后这就是故事的结尾,将。该标准最常见的解释是init
类型的值写入该位置。但是标准不清楚void init(s* v) { s a = { .a = 1, .b = 2 }; *v = a; }
设置了哪个有效类型
s
表示v->a = 1;
,并且此分配还具有为整个v->a = 1
对象设置有效类型的“副作用”。 此解释使TBAA在类似(*v).a = 1;
的功能(其中*v
是没有类型void f(s *ps, t* pt)
的成员的结构)上假设s
和t
是不相交的。我确定后面的观点对此已经有多个问题