【问题标题】:flexible array in C and dereferencing type-punned pointer errorC中的灵活数组和取消引用类型双关指针错误
【发布时间】:2014-07-30 15:54:10
【问题描述】:

当我尝试使用 gcc -O3 -Wall -Werror -std=c99 main.c 编译下面的代码时,我在 #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; // ???

【问题讨论】:

  • 发出警告,因为它违反了规则。您是否对为什么前两个案例没有触发警告感兴趣,或者正在寻找解决方法?
  • 我很感兴趣为什么第一个案例没有触发警告。我知道两种解决方法:联合和“组合继承”。 (对不起,我的英语不好:)

标签: c strict-aliasing flexible-array-member


【解决方案1】:

首先,它与灵活数组无关,而是与任何数组有关。您可以使用固定大小足够大的数组并看到相同的结果。

最明显的解决方法是您的BASE_CAST_2。另一种可能是使用offsetof 而不是sizeof,尤其是在结构尾部不灵活的情况下。

第一种情况和第三种情况之间的区别很棘手。它们都违反了严格的别名规则,但是当 gcc 无法确定左值的来源时,它有时会允许这样做。但是,使用-Wstrict-alising=2 它会在这两种情况下发出警告。我不确定即使没有使用标准-Wstrict-aliasing 发出警告,它也能保证生成有效代码(但在给定的示例中确实如此)。

第二种情况看起来不错,因为允许将structure* 转换为char*。但是,char*char[] 类型之间存在差异。

【讨论】:

  • 感谢您的回答。你说第二种情况看起来不错,因为允许将结构*转换为字符*。但是为什么((struct _type *)((char *)((_obj)-&gt;rest))) 不起作用?
  • 在任何强制转换之前检查别名(嗯,取决于警告级别)。
【解决方案2】:

它们都通过将 char 数组别名为非 char 类型来打破严格别名。 (虽然反过来也是允许的)。

它们都可能存在对齐问题; rest 可能未正确对齐结构。

您可能不会收到警告,因为:

  • 编译器对别名违规的检测不是很好,和/或
  • 您的系统实际上允许这种别名,即使它是不可移植的

如果您将rest 替换为指向动态分配内存的指针,那么这两个问题都会消失,因为动态分配内存的“有效类型”取决于您在其中存储的内容。

注意,在我看来这是一个更好的解决方案:

struct Concrete
{
    struct Base base;
    char const *str_bar;
};

或者,保持混凝土原样:

struct BaseConcrete
{
    struct Base base;
    struct Concrete concrete;
};

【讨论】:

  • 感谢关于结构对齐的注释和char const *str_bar =)
猜你喜欢
  • 2018-05-31
  • 2017-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多