1

以下代码在使用 gcc 4.1 版和 -O2 标志编译时会出错。它可以与 gcc 版本 4.4、4.5 等一起编译。

错误是: warning: dereferencing type-punned pointer will break strict-aliasing rules

void foo(void **a = NULL);

int main()
{
    int *a;
    foo((void **)&a);  //I get above error here

    cout << "a[0] " << *a << endl;
    cout << "a[1] " << *(a+1) << endl;
    cout << "a[2] " << *(a+2) << endl;
    return 0;
}

void foo(void **a)
{
    int  b[3];
    b[0] = 10; b[1] = 20; b[2] = 35;
    if(a != NULL) {
         *a = (char *)malloc(20);
         memcpy((char *)(*a),  &b, 12);
    }

}

现在为了避免这种情况,我可以像下面给出的那样编程。这是避免此警告的好方法吗?我可以在这段代码中避免这个警告。

void foo2(char **a = NULL);

int main()
{
    char *a; 
    float c[3];
    foo2(&a);
    memcpy(&c, a, sizeof(c));
    cout << "c[0] " << *c << endl;
    cout << "c[1] " << *(c+1) << endl;
    cout << "c[2] " << *(c+2) << endl;
    return 0;
}

void foo2(char **a)
{
    float  c[3];
    c[0] = 10.123; c[1] = 2.3450; c[2] = 435.676;
    if(a != NULL) {
        *a = (char *)malloc(sizeof(c));
        memcpy((char *)(*a),  &c, sizeof(c));
    }
}
4

1 回答 1

4

编译器抱怨您正在使用 cast an 传递int **给接受的函数void **,并且根据标准,编译器不需要检查通过 a 进行的写入void **是否会影响 type 的对象int *

Anint *和 avoid *是不同的类型,编译器不需要检查它们之间可能存在的别名。作为一个实际的具体示例,请考虑:

int *a = &one_thing;
void **b = (void **)&a;
bar(a);
(*b) = &other_thing;
baz(a); // Here the compiler may assume `a` didn't change

另请注意,该标准保证您可以将任何指向对象的指针void *安全地转换为 a 并返回,但是您的代码将 a 转换int**void**和返回:这不是一回事,不能保证安全。

这个memcpy案例很有趣,因为函数形式上接受void *值并且 avoid *不能用于取消引用任何内容。然而,IIUC 承认memcpy一次会读/写一个(无符号)char,因此编译器被迫假设在memcpy调用之后可能被覆盖的任何类型的对象现在可以有一个新值(有一个特殊的标准中规定 achar *和 anusigned char *可以为任何类型的值取别名)。

所以是的,使用memcpy你可以围绕类型系统做任何你喜欢的讨厌的事情,并且不允许编译器忽略/重新排序你的写操作。我在某处读过编译器甚至可以检测memcpy调用并实际生成合理的代码,因此只有语法(而不是性能)会很糟糕。

于 2013-07-16T12:32:37.300 回答