当我尝试使用gcc -O3 -Wall -Werror -std=c99 main.c
编译下面的代码时,我得到一个错误,例如“取消引用类型惩罚指针会破坏严格别名规则”在#3,但不会在#3中2或#1。我可以取消引用类型惩罚“char *”,但为什么我不能对灵活的数组做同样的事情呢?
#include <stdlib.h>
#include <stdio.h>
struct Base {
void (*release) (struct Base *);
size_t sz;
char *str_foo;
char rest[];
};
struct Concrete {
char *str_bar;
};
void
Base_release(struct Base *base)
{
free(base);
}
struct Base *
Base_new(size_t sz_extra)
{
size_t sz = sizeof(struct Base) + sz_extra;
struct Base *base = (struct Base *)malloc(sz);
base->release = &Base_release;
base->sz = sz;
base->str_foo = "foo";
return base;
}
#define BASE_FREE(_obj) (_obj)->release(_obj)
#define BASE_CAST(_type, _obj) ((struct _type *)((_obj)->rest))
#define BASE_CAST_2(_type, _obj) ((struct _type *)((char *)(_obj)+sizeof(struct Base)))
struct Base *
Concrete_new()
{
struct Base *base = Base_new(sizeof(struct Concrete));
struct Concrete *concrete = BASE_CAST(Concrete, base);
concrete->str_bar = "bar";
return base;
}
int main(int argc, const char *argv[])
{
struct Base *instance = Concrete_new();
printf("Base str: %s\n", instance->str_foo);
// #1 - Legal
struct Concrete *cinstance = BASE_CAST(Concrete, instance);
printf("#1: Concrete str: %s\n", cinstance->str_bar);
// #2 - Legal
printf("#2: Concrete str: %s\n", BASE_CAST_2(Concrete, instance)->str_bar);
// #3 - Compile error
printf("#3: Concrete str: %s\n", BASE_CAST(Concrete, instance)->str_bar);
BASE_FREE(instance);
return 0;
}
编辑1: 下面有更具体的例子显示问题:
struct s {
char a;
};
char *const a = malloc(sizeof(struct s));
char b[sizeof(struct s)];
((struct s *)((char *)a))->a = 5; // This is a valid case
((struct s *)(a))->a = 5; // OK
((struct s *)((char *)b))->a = 5; // ???
答案 0 :(得分:2)
首先,它与灵活数组无关,而是与任何数组有关。你可以使用固定大小足够的数组并看到相同的结果。
最明显的解决方法是BASE_CAST_2
。另一个可能是使用offsetof
而不是sizeof
,尤其是在结构尾部不灵活的情况下。
第一和第三种情况之间的区别很棘手。它们都破坏了严格的别名规则,但是当它无法确定左值的来源时,gcc有时会允许这样做。但是,对于-Wstrict-alising=2
,它会在两种情况下发出警告。即使没有使用标准-Wstrict-aliasing
发出警告,我也不确定是否能保证生成有效代码(但在该示例中它是这样做的。)
第二种情况看起来不错,因为允许将structure*
强制转换为char*
。但是char*
和char[]
类型之间存在差异。
答案 1 :(得分:1)
它们都通过将char数组别名为非char类型来破坏严格别名。 (尽管如此,允许相反)。
他们都可能有对齐问题; rest
可能未正确对齐结构。
您可能不会收到警告,因为:
如果用一个指向动态分配内存的指针替换rest
,则这两个问题都会消失,因为&#34;有效类型&#34;动态分配的内存由您存储在其中的内容决定。
请注意,在我看来,这是一个更好的解决方案:
struct Concrete
{
struct Base base;
char const *str_bar;
};
或者,保持混凝土,你可以做:
struct BaseConcrete
{
struct Base base;
struct Concrete concrete;
};