【发布时间】:2020-04-14 14:48:46
【问题描述】:
C 中的“结构继承技术”(as described in this question) 之所以成为可能,是因为 C 标准保证结构的第一个成员在它之前永远不会有任何填充 (?),并且第一个成员将始终等于结构本身的地址。
这允许如下用法:
typedef struct {
// some fields
} A;
typedef struct {
A base;
// more fields
} B;
typedef struct {
B base;
// yet more fields
} C;
C* c = malloc(sizeof(C));
// ... init c or whatever ...
A* a = (A*) c;
// ... access stuff on a etc.
B* b = (B*) c;
// ... access stuff on b etc.
这个问题有两个部分:
A.在我看来,这种技术打破了严格的别名规则。我错了吗?如果是,为什么?
B.假设这种技术确实是合法的。在这种情况下,如果 A: 我们首先将对象存储在其特定类型的左值中,然后向下或向上转换为不同的类型,或者 B: 如果我们直接将其转换为当前所需的特定类型,而不首先将其存储在特定类型的左值中?
例如,这三个选项是否都同样合法?
选项 1:
C* make_c(void) {
return malloc(sizeof(C));
}
int main(void) {
C* c = make_c(); // First store in a lvalue of the specific type
A* a = (A*) c;
// ... do stuff with a
C* c2 = (C*) a; // Cast back to C
// ... do stuff with c2
return 0;
}
选项 2:
C* make_c(void) {
return malloc(sizeof(C));
}
int main(void) {
A* a = (A*) make_c(); // Don't store in an lvalue of the specific type, cast right away
// ... do stuff with a
C* c2 = (C*) a; // Cast back to C
// ... do stuff with c2
return 0;
}
选项 3:
int main(void) {
A* a = (A*) malloc(sizeof(C)); // Don't store in an lvalue of the specific type, cast right away
// ... do stuff with a
C* c2 = (C*) a; // Cast to C - even though the object was never actually stored in a C* lvalue
// ... do stuff with c2
return 0;
}
【问题讨论】:
-
所以对于问题 A,在您的示例中,您是否看到一个对象的存储值被不属于 6.5.7 中列出的类型之一的左值表达式访问(在 N2176 中) ?
-
@NateEldredge 所以让我们以
A* a = (A*) c; // ... access stuff on a etc.为例。假设我们用a->x = 10;代替注释(假设(A有一个int x字段)。我们是否认为a->...等同于取消引用a,这会产生A类型的左值,而底层字节的有效类型是int,或者C? -
几年前,我在某处与几位 C 语言律师一起参与了一个关于此的长线程。抱歉我现在找不到了。结果是您建议的是调用未定义的行为。唯一完全一致的转换是从第一个元素到封闭结构。不是其他方式。结果是您需要在每个“子类”中声明相同的第一个字段(逻辑上指示其实际类型)并将指向该字段的指针作为基类引用传递。
-
这种情况下指针的转换与严格的别名规则无关。给定
c指向一些初始化的C,考虑c->base访问C中的A对象,并且在A *a = &c->base;之后,使用a->SomeField是完全正常的,没有混叠冲突。A *a = (A *) c获得与A *a = &c->base;相同的指针,只是以另一种方式。不管这个a是如何获得的,它都是指向一个已建立结构中的已建立成员,并且使用它符合访问左值的规则。 -
类似地,在
C *c2 = (C *) a;之后,我们有一个c2指向一个已建立的C。 指针来自一个指向A的指针这一事实并不能否定我们现在有一个指向C的指针这一事实,它实际上指向一个C,即,它指向有效类型为C的内存,因此使用c2访问C符合访问左值的规则。