4

以下のコードをコンパイルしようとすると、#3 で「逆参照すると、厳密なエイリアス規則が破られます」gcc -O3 -Wall -Werror -std=c99 main.cのようなエラーが表示されますが、#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; // ???
4

2 に答える 2

2

まず第一に、それは柔軟な配列ではなく、任意の配列に関連しています。固定サイズの十分な大きさの配列を使用して、同じ結果を確認できます。

最も明白な回避策はあなたのBASE_CAST_2. 特に構造体のテールが柔軟でない場合は、offsetofの代わりに別のものを使用できます。sizeof

1 番目と 3 番目のケースの違いは難しいです。どちらも厳密なエイリアシング規則に違反していますが、gcc は左辺値の起源を特定できない場合にそれを許可することがあります。ただし、-Wstrict-alising=2どちらの場合も警告が表示されます。標準で警告が発行されなくても、有効なコードを生成することが保証されているかどうかはわかりません-Wstrict-aliasing(ただし、その例ではそうです)。

structure*へのキャストchar*が許可されているため、2 番目のケースは問題ないように見えます。char*ただし、とタイプには違いがありchar[]ます。

于 2014-07-30T17:53:11.893 に答える
1

それらはすべて、char の配列を char ではない型としてエイリアス化することにより、厳密なエイリアス化を破ります。(その逆も許されます)。

また、それらはすべて位置合わせの問題を抱えている可能性があります。rest構造体に対して正しく配置されていない可能性があります。

次の理由により、警告が表示されない場合があります。

  • コンパイラによるエイリアシング違反の検出がうまくいかない、および/または
  • あなたのシステムは、移植性がないにもかかわらず、実際にこのエイリアシングを許可しています

動的に割り当てられたメモリへのポインタに置き換えた場合rest、動的に割り当てられたメモリの「有効なタイプ」はそこに格納するものによって決定されるため、これらの問題は両方ともなくなります。

注意してください、それは私にははるかに良い解決策のようです:

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

または、Concrete をそのままにして、次のようにします。

struct BaseConcrete
{
    struct Base base;
    struct Concrete concrete;
};
于 2014-07-30T22:06:34.137 に答える